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 |
![]() ![]() |