| I l@ve RuBoard |
|
5.3 Polymorphism without InheritanceThe num_sequence class of Section 4.10 simulates polymorphism. Each class object can be made into any of the six numerical sequences at any point in the program through the set_sequence() member function:
for ( int ix = 1; ix < num_sequence::num_of_sequences(); ++ix )
{
ns.set_sequence( num_sequence::nstype( ix ));
int elem_val = ns.elem( pos );
// ...
}
The ability to change the sequence type of ns is supported through programming rather than through direct support of the language. Each class object contains an _isa data member that identifies the current numeric sequence that it represents:
class num_sequence {
public:
// ...
private:
vector<int> *_elem; // addresses current element vector
PtrType _pmf; // addresses current element generator
ns_type _isa; // identifies current sequence type
// ...
};
_isa is set to a named constant value that represents one of the supported numeric sequence types. The constant values are grouped in an enumerated type I've named ns_type:
class num_sequence {
public:
enum ns_type {
ns_unset, ns_fibonacci, ns_pell, ns_lucas,
ns_triangular, ns_square, ns_pentagonal
};
// ...
};
nstype() verifies that its integer parameter represents a valid numeric sequence value. If it does, it returns the associated enumerator; otherwise, it returns ns_unset:
class num_sequence {
public:
// ...
static ns_type nstype( int num )
{
return num <= 0 || num >= num_seq
? ns_unset // invalid value
: static_cast< ns_type >( num );
}
};
The static_cast is a special conversion notation. It converts the integer num to its associated ns_type enumerator. The result of nstype() is passed to set_sequence(): ns.set_sequence( num_sequence::nstype( ix )); set_sequence() does the work of setting _pmf, _isa, and _elem data members to the correct numeric sequence:
void num_sequence::
set_sequence( ns_type nst )
{
switch ( nst )
{
default:
cerr << "invalid type: setting to 0\n";
// deliberate fall-through
case ns_unset:
_pmf = 0;
_elem = 0;
_isa = ns_unset;
break;
case ns_fibonacci: case ns_pell: case ns_lucas:
case ns_triangular: case ns_square: case ns_pentagonal:
// func_tbl: table of pointer to member functions
// seq: vector of vectors holding sequence elements
_pmf = func_tbl[ nst ];
_elem = &seq[ nst ];
_isa = nst;
break;
}
}
To support a query as to the numeric sequence to which an object is currently set, I provide a what_am_i() operation that returns a character string identifying the current numeric sequence. For example,
inline void display( ostream &os, const num_sequence &ns, int pos )
{
os << "The element at position "
<< pos << " for the "
<< ns.what_am_i() << " sequence is "
<< ns.elem( pos ) << endl;
}
what_am_i() indexes _isa into a static character string array that lists the supported numeric sequence names in the order of the ns_type enumerators:
const char* num_sequence::
what_am_i() const
{
static char *names[ num_seq ] = {
"notSet",
"fibonacci", "pell",
"lucas", "triangular",
"square", "pentagonal"
};
return names[ _isa ];
}
This is quite a lot of work, particularly in terms of maintenance. Each time we wish to add to or delete a numeric sequence type, all the following must be updated correctly: the vector of element vectors, the array of pointer to member functions, the what_am_i() string array, the switch statement of set_sequence(), the value of num_seq, and so on. Under the object-oriented programming model, this sort of explicit programming overhead is eliminated, making our code simpler and more extensible. |
| I l@ve RuBoard |
|