In this section, we will look at some simple examples in Java that illustrate how we may use the mechanisms of inheritance and (simple) delegation to define objects that have similar functionality but differ in the way that functionality is realized. These examples prepare the way for the more complex idioms and patterns presented later in this chapter.
In the rest of this section we will look briefly at the polymorphic constructs offered by C++. We will also study how behavioral conformance can be enforced in C++ by including invariants and assertions. These sections may be skipped on first reading.
public class envelope {public envelope() { } public void message() { System.out.println("hello ... "); } };
envelope
We will proceed in three steps: (1) The envelope class will be redesigned so that it acts only as an interface to the letter implementation class. (2) Then we introduce a factory object, that is used to create envelope and letter instances. (3) Finally, we refine the letter class into a singleton class, that prevents the creation of multiple letter instances.
public class envelope {letter impl; public envelope() { impl = new letter(); } public void message() { impl.message(); } };
envelope
public class letter {public letter() { } public void message() { System.out.println("Message in a letter"); } };
letter
public class factory {public factory() { } letter letter() { return new letter(); } envelope envelope() { return new envelope(); } };
factory
public class envelope {letter impl; public envelope() { factory f = new factory(); impl = f.letter(); // obtained from factory } public void message() { impl.message(); } };
envelope
public class singleton extends letter {static int number = 0; protected singleton() { } static letter instance() { if (number==0) { theletter = new letter(); number = 1; } return theletter; } public void message() { System.out.println("Message in a letter"); } static letter theletter; };
singleton
extern void print(int); extern void print(float);
template< class T > class list { ... } list<int>* alist;
class shape { ... }; class circle : public shape { ... } shape* s = new circle;
STL is supported by C++ compilers that adhere to the C++
standard, including Microsoft Visual C++ and the Cygnus/GNU C++ compilers.
A more extensive discussion of STL is beyond the scope of this
book, but the reader is advised to consult
In the example below, assertions are used to check for the satisfying of both the pre- and post-conditions of a function that computes the square root of its argument, employing a method known as Newton iteration.
double sqrt( double arg ) {require ( arg >= 0 ); double r=arg, x=1, eps=0.0001; while( fabs(r - x) > eps ) { r=x; x=r-((r*r-arg)/(2*r)); } promise ( r - arg * arg <= eps ); return r; }
sqrt
Whereas Eiffel directly supports the use of assertions by allowing access to the value of an instance variable before the execution of a method through the keyword old, the C++ programmer must rely on explicit programming to be able to compare the state before an operation with the state after the operation.
class counter {public: counter(int n = 0) : _n(n) { require( n >= 0 ); promise( invariant() );
counter check initial state
} virtual void operator++() { require( true );empty pre-condition
hold();save the previous state
_n += 1; promise( _n == old_n + 1 && invariant() ); } int value() const { return _n; }no side effects
virtual bool invariant() { return value() >= 0; } protected: int _n; int old_n; virtual void hold() { old_n = n; } };
Assertions may also be used to check whether the
object is correctly initialized.
The pre-condition stated in the constructor
requires that the counter must start with
a value not less than zero.
In addition, the constructor checks whether
the class invariant, stated in the (virtual)
member function invariant, is satisfied.
Similarly, after checking whether the post-condition
of the
class bounded : public counter {public: bounded(int b = MAXINT) : counter(0), max(b) {} void operator++() { require( value() < max );
bounded to prevent overflow
counter::operator++(); } bool invariant() { return value() <= max && counter::invariant(); } private: int max; };
From a formal perspective, the use of assertions may be regarded as a way of augmenting the type system supported by object-oriented languages. More importantly, from a software engineering perspective, the use of assertions is a means to enforce contractual obligations.
Having studied the mechanisms, the next step is to find proper ways,
recipes as it were, to use these mechanisms.
What we need, in the terminology of
In this section, we will look at the concrete class idiom for C++, which states the ingredients that every class must have to behave as if it were a built-in type. Other idioms, in particular an improved version of the handle/body or envelope/letter idiom that may be used to separate interface from implementation, will be treated in the next section.
Abstract data types must be indistinguishable
from built-in types
To verify whether a concrete data type meets the requirements imposed by the specification of the abstract data type is quite straightforward, although not always easy. However, the task of verifying whether a concrete data type is optimally implemented is rather less well defined. To arrive at an optimal implementation may involve a lot of skill and ingenuity, and in general it is hard to decide whether the right choices have been made. Establishing trade-offs and making choices, for better or worse, is a matter of experience, and crucially depends upon the skill in handling the tools and mechanisms available.
When defining concrete data types, the list of requirements defining the canonical class idiom given in slide 2-canonical may be used as a check list to determine whether all the necessary features of a class have been defined. Ultimately, the programmer should strive to realize abstract data types in such a way that their behavior is in some sense indistinguishable from the behavior of the built-in data types. Since this may involve a lot of work, this need not be a primary aim in the first stages of a software development project. But for class libraries to work properly, it is simply essential.