Adding new observers

Instructor's Guide


intro, types, algebra, modules, classes, summary, Q/A, literature
Now, for the complementary case, what happens when we add new observers to the specification of a data type? Somewhat surprisingly, the object-oriented approach now seems to be at a disadvantage. Since in a module realization of an abstract data type the code is organized around observers, adding a new observer function amounts simply to adding a new operation with a case for each of the possible generator types, as shown in slide 8-mod-obs.

Adding new observers

ADT


  int length( list* l ) { 
length
switch( l->tag ) { case NIL: return 0; case CONS: return 1 + length(l->next); case INTERVAL: return l->z - l->e + 1; }; }

slide: Modules and observers

When we look at how we may extend a given object realization of an abstract data type with a new observer we are facing a problem. The obvious solution is to modify the source code and add the length function to the list interface class and each of the generator classes. This is, however, against the spirit of object orientation and may not always be feasible. Another, rather awkward solution, is to extend the collection of possible generator subtypes with a number of new generator subtypes that explicitly incorporate the new observer function. However, this also means redefining the tail function since it must deliver an instance of a list with length class. As a workaround, one may define a function length and an extended version of the list template class supporting only the length (observer) member function as depicted in slide 8-oop-obs.

Adding new observers

OOP


  template< class E >  
  int length(list< E >* l) { 
length
return l->empty() ? 0 : 1 + length( l->tail() ); } template< class E > class listWL : public list<E> {
listWL
public: int length() { return ::length( this ); } };

slide: Objects and observers

A program fragment illustrating the use of the listWL class is given below.
  list<int>* r = new cons<int>(1,new cons<int>(2,new interval(3,7))); 
  while (! r->empty()) { 
  	cout << ((listWL< int >*)r)->length() << endl;
  	r = r->tail();
  	}
  delete r;
  
Evidently, we need to employ a cast whenever we wish to apply the length observer function. Hence, this seems not to be the right solution. Alternatively, we may use the function length directly. However, we are then forced to mix method syntax of the form ref->op(args) with function syntax of the form fun(ref,args), which may easily lead to confusion.

Discussion

We may wonder why an object-oriented approach, that is supposed to support extensibility, is at a disadvantage here when compared to a more traditional module-based approach. As observed in  [Cook90], the problem lies in the fact that neither of the two approaches reflect the full potential and flexibility of the matrix specification of an abstract data type. Each of the approaches represents a particular choice with respect to the decomposition of the matrix, into either an operations-oriented (horizontal) decomposition or a data-oriented (vertical) decomposition. The apparent misbehavior of an object realization with respect to extending the specification with observer functions explains why in some cases we prefer the use of overloaded functions rather than methods, since overloaded functions allow for implicit dispatching to take place on multiple arguments, whereas method dispatching behavior is determined only by the type of the object. However, it must be noted that the dispatching behavior of overloaded functions in C++ is of a purely syntactic nature. This means that we cannot exploit the information specific for a class type as we can when using virtual functions. Hence, to employ this information we would be required to write as many variants of overloaded functions as there are combinations of argument types. Dynamic dispatching on multiple arguments is supported by multi-methods in CLOS, see  [Paepcke93]. According to  [Cook90], the need for such methods might be taken as a hint that objects only partially realize the true potential of data abstraction.