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
and .
This could easily be simplified by extending the function
and
with the statements
and 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 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.