Abstract systems and events

Instructor's Guide


drawtool, design, specification, summary, Q/A, literature
User actions may require complex interactions between the objects constituting the object model of a system. Such interactions are often of an ad hoc character in the sense that they embody one of the many possible ways in which the functionality of objects may be used. What we need is a methodology or paradigm that allows us to express these interactions in a concise yet pragmatically amenable way. In  [Henderson93], a notion of abstract systems is introduced that seems to meet our needs to a large extent. See slide 3-abstract.

Abstract systems -- design methodology

Events -- high level glue


slide: Abstract systems and events

Abstract systems extend the notion of abstract data types to capture the (possible) interactions between collections of objects. The idea underlying the notion of an abstract system is to collect the commands available for the client or user of the system. The collection of commands comprising an abstract system are usually a (strict) subset of the commands available in the combined interface of the abstract data types involved. In other words, an abstract system provides a restricted interface, restricted to safeguard the user from breaking the protocol of interaction implicitly defined by the collection of abstract data types of which the system consists. An abstract system in itself merely provides a guideline on how a collection of objects is to be used, but does not offer a formal means to check whether a user plays by the rules. After presenting an example of an abstract system, we will look at how events may be used to protect the user against breaking the (implicit) laws governing the interaction.

Example -- the library

The abstract system comprising a library may be characterized as in slide 3-library. In essence, it provides an exemplary interface, that is, it lists the statements that are typically used by a client of the library software. We use typical identifiers to denote objects of the various types involved.

Abstract system -- exemplary interface

library


  p = new person();
  b = new book();
  p = b->borrower;
  s = p->books;
  tf = b->inlibrary();
  b->borrow(p);
  p->allocate(b);
  p->deallocate(b);
  b->_return(p);
  

For person* p; book* b; set<book>* s; bool tf;


slide: The library system

The commands available to the user of the library software are constructors for a person and a book, an instruction to get access to the borrower of a particular book, an instruction to ask what books a particular person has borrowed, an instruction to query whether a particular book is in the library, and instructions for a person to borrow or return a book. To realize the abstract system library, we evidently need the classes book and person. The class book may be defined as follows.
  class book { 
book
public: person* borrower; book() {} void borrow( person* p ) { borrower = p; } void _return( person* p ) { borrower = 0; } bool inlibrary() { return !borrower; } };
It consists of a constructor, functions to borrow and return a book, a function to test whether the book is in the library and an instance variable containing the borrower of the book. Naturally, the class book may be improved with respect to encapsulation (by providing a method to access the borrower) and may further be extended to store additional information, such as the title and publisher of the book.
  class person { 
person
public: person() { books = new set(); } void allocate( book* b ) { books->insert(b); } void deallocate( book* b ) { books->remove(b); } set* books; };
The next class involved in the library system is the class person, given above. The class person offers a constructor, an instance variable to store the set of books borrowed by the person and the functions allocate and deallocate to respectively insert and remove the books from the person's collection. A typical example of using the library system is given below.
  book* Stroustrup = new book(); 
example
book* ChandyMisra = new book(); book* Smalltalk80 = new book(); person* Hans = new person(); person* Cees = new person(); Stroustrup->borrow(Hans); Hans->allocate(Stroustrup); ChandyMisra->borrow(Cees); Cees->allocate(ChandyMisra); Smalltalk80->borrow(Cees); Cees->allocate(Smalltalk80);
First, a number of books are defined, then a number of persons, and finally (some of) the books that are borrowed by (some of) the persons.

Note that lending a book involves both the invocation of book::borrow and person::allocate. This could easily be simplified by extending the function book::borrow and book::_return with the statements p->allocate(this) and p->deallocate(this) respectively. However, I would rather take the opportunity to illustrate the use of events, providing a generic solution to the interaction problem noted.

Events

 [Henderson93] introduces events as a means by which to control the complexity of relating a user interface to the functionality provided by the classes comprising the library system. The idea underlying the use of events is that for every kind of interaction with the user a specific event class is defined that captures the details of the interaction between the user and the various object classes. Abstractly, we may define an event as an entity with only two significant moments in its life-span, the moment of its creation (and initialization) and the moment of its activation (that is when it actually happens). As a class we may define an event as follows.
  class Event { 
Event
public: virtual void operator()() = 0; };
The class Event is an abstract class, since the application operator that may be used to activate the event is defined as zero.
  class Borrow : public Event { 
Borrow
public: Borrow( person* _p, book* _b ) { _b = b; _p = p; } void operator()() { require( _b && _p );
_b and _p exist
_b->borrow(p); _p->allocate(b); } private: person* _p; book* _b; };
For the library system defined above we may conceive of two actual events (that is, possible refinements of the Event class), namely a Borrow event and a Return event.

The Borrow event class provides a controlled way in which to effect the borrowing of a book. In a similar way, a Return event class may be defined.

  class Return : public Event { 
Return
public: Return( person* _p, book* _b ) { _b = b; _p = p; } void operator()() { require( _b && _p ); _b->_return(p); _p->deallocate(b); } private: person* _p; book* _b; };
The operation Has specified in the previous section has an immediate counterpart in the person::books data member and need not be implemented by a separate event.

Events are primarily used as intermediate between the user (interface) and the objects comprising the library system. For the application at hand, using events may seem to be somewhat of an overkill. However, events not only give a precise characterization of the interactions involved but, equally importantly, allow for extending the repertoire of interactions without disrupting the structure of the application simply by introducing additional event types.