To look at an example implementation, recall the motivating scenario: there are classes whose assistance we wish to enlist to create a flexible debugging environment. This implementation uses the External Polymorphism pattern to define a mechanism by which all participating objects (1) can be collected in a central ``in-memory'' object collection and (2) can dump their state upon request.
The Dumpable class forms the base of the hierarchy and defines the desired polymorphic interface which, in this case, is for dumping:
0=6 =0.850 .55 -0 =.9
0
class Dumpable
{
public:
Dumpable (const void *);
// This pure virtual method must be
// implemented by a subclass.
virtual void dump (void) const = 0;
};
ObjectCollection is the client-a simple collection that holds handles to objects. The class is based on the STL vector class [4].
0=6 =0.850 .55 -0 =.9
0
class ObjectCollection : public vector<Dumpable>
{
public:
// Iterates through the entire set of
// registered objects and dumps their state.
void dump_objects (void);
};
The dump_objects method can be implemented as follows:
0=6 =0.850 .55 -0 =.9
0
void
ObjectCollection::dump_objects (void)
{
struct DumpObject {
bool operator()(const Dumpable*& dp) {
dp->dump();
}
};
for_each(begin(), end(), DumpObject());
}
Now that the foundation has been provided, we can define ConcreteDumpable:
0=6 =0.850 .55 -0 =.9
0
template <class ConcreteType>
class ConcreteDumpable : public Dumpable
{
public:
ConcreteDumpable (const ConcreteType* t);
virtual void dump (void) const; // Concrete dump method
private:
const ConcreteType* realThis_; // Pointer to actual object
};
The ConcreteDumpable methods are implemented as follows:
0=6 =0.850 .55 -0 =.9
0
template <class ConcreteType>
ConcreteDumpable<ConcreteType>::ConcreteDumpable
(const ConcreteType* t)
: realThis_ (t)
{
}
template <class ConcreteType> void
ConcreteDumpable<ConcreteType>::dump (void) const
{
realThis_->dump<ConcreteType>(realThis_);
}
All that's left are the signature adapters. Suppose that SOCK_Stream and SOCK_Acceptor both have a dump method that outputs to cerr. INET_Addr, on the other hand, has a printTo method that takes the output stream as the sole argument. We could define two signature adapters. The first is a generic signature adapter that works with any concrete type that defines a dump method:
0=6 =0.850 .55 -0 =.9
0
template <class ConcreteType> void
dump<ConcreteType>(const ConcreteType* t)
{
t->dump();
}
whereas the second is specialized signature adapter that is customized for INET_Addr (which does not support a dump method):
0=6 =0.850 .55 -0 =.9
0
void
dump<INET_Addr>(const INET_Addr* t)
{
t->printTo(cerr);
}
The ObjectCollection instance can be populated by instances of ConcreteDumpable<> by making calls such as:
0=6 =0.850 .55 -0 =.9
0
... ObjectCollection oc; // Have instances of various SOCK_Stream, etc., types ... oc.insert(oc.end(), aSockStream); oc.insert(oc.end(), aSockAcceptor); oc.insert(oc.end(), aInetAddr); ...
Then, later, we could query the state of those objects simply by calling 0=6 =0.850 .55 -0 =.9
0
... oc.dump_objects(); ...