FAQs in section [17]:
[17.1] What's the big deal of separating interface from
implementation?
Interfaces are a company's most valuable resources. Designing
an interface takes longer than whipping together a concrete class
which fulfills that interface. Furthermore interfaces require the
time of more expensive people.
Since interfaces are so valuable, they should be protected
from being tarnished by data structures and other implementation
artifacts. Thus you should separate interface from implementation.
[ Top | Bottom | Previous section | Next section ]
[17.2] How do I separate interface from implementation in C++
(like Modula-2)?
Use an ABC.
[ Top | Bottom | Previous section | Next section ]
[17.3] What is an ABC?
An abstract base class.
At the design level, an abstract base class (ABC) corresponds
to an abstract concept. If you asked a mechanic if he repaired
vehicles, he'd probably wonder what kind-of vehicle you
had in mind. Chances are he doesn't repair space shuttles, ocean
liners, bicycles, or nuclear submarines. The problem is that the
term "vehicle" is an abstract concept (e.g., you can't
build a "vehicle" unless you know what kind of vehicle
to build). In C++, class Vehicle would be an
ABC, with Bicycle, SpaceShuttle, etc, being
subclasses (an OceanLiner is-a-kind-of-a Vehicle).
In real-world OO, ABCs show up all over the place.
At the programming language level, an ABC is a class
that has one or more pure virtual
member functions. You cannot make an object (instance) of an ABC.
[17.4] What is a "pure virtual" member function?
A member function declaration that turns a normal class into
an abstract class (i.e., an ABC). You normally only implement it
in a derived class.
Some member functions exist in concept; they don't have any
reasonable definition. E.g., suppose I asked you to draw a Shape
at location (x,y) that has size 7. You'd ask me "what
kind of shape should I draw?" (circles, squares, hexagons,
etc, are drawn differently). In C++, we must indicate the
existence of the draw() member function (so users can
call it when they have a Shape* or a Shape&),
but we recognize it can (logically) be defined only in subclasses:
class Shape {
public:
virtual void draw() const = 0; // = 0 means it is "pure virtual"
// ...
};
This pure virtual function makes Shape an ABC. If you
want, you can think of the "= 0;" syntax
as if the code were at the NULL pointer. Thus Shape
promises a service to its users, yet Shape isn't able to provide
any code to fulfill that promise. This forces any actual
object created from a [concrete] class derived from Shape to have
the indicated member function, even though the base class doesn't
have enough information to actually define it yet.
Note that it is possible to provide a definition for a pure
virtual function, but this usually confuses novices and is best
avoided until later.
[17.5] How do you define a copy constructor or assignment operator
for a class that contains a pointer to a (abstract) base class?
If the class "owns" the object pointed to by the (abstract)
base class pointer, use the Virtual Constructor Idiom
in the (abstract) base class. As usual with this idiom, we
declare a pure virtual clone()
method in the base class:
class Shape {
public:
// ...
virtual Shape* clone() const = 0; // The Virtual (Copy) Constructor
// ...
};
Then we implement this clone() method in each derived
class:
class Circle {
public:
// ...
virtual Shape* clone() const { return new Circle(*this); }
// ...
};
class Square {
public:
// ...
virtual Shape* clone() const { return new Square(*this); }
// ...
};
Now suppose that each Fred object "has-a" Shape
object. Naturally the Fred object doesn't know whether
the Shape is Circle or a Square or ...
Fred's copy constructor and assignment operator
will invoke Shape's clone() method to copy the
object:
class Fred {
public:
Fred(Shape* p) : p_(p) { assert(p != NULL); } // p must not be NULL
~Fred() { delete p_; }
Fred(const Fred& f) : p_(f.p_->clone()) { }
Fred& operator= (const Fred& f)
{
if (this != &f) { // Check for self-assignment
Shape* p2 = f.p_->clone(); // Create the new one FIRST...
delete p_; // ...THEN delete the old one
p_ = p2;
}
return *this;
}
// ...
private:
Shape* p_;
};
|