class account {
public:
account() { _balance = 0; promise(account::invariant()); }
virtual float balance() const { return _balance; }
void deposit(float x) {
require( x >= 0 ); check precondition
hold(); to save the old state
_balance += x;
promise( balance() == old_balance + x && invariant() );
}
void withdraw(float x) {
require( x <= balance() ); check precondition
hold(); to save the old state
_balance -= x;
promise( balance() == old_balance - x && invariant() );
}
protected:
virtual bool invariant() { return balance() >= 0; }
virtual void hold() { old_balance = balance(); }
private:
float _balance;
float old_balance; additional variable
};
A credit_account may be considered to be an improvement over an ordinary account since it allows us to draw more money from it. One possible realization is given below.
class credit_account : public account {
public:
credit_account(float x) { _maxcredit = x; _credit = 0; }
float balance() { return _balance + _credit; }
float credit(float x) {
require( x >= 0 && x + _credit <= _maxcredit );
hold();
_credit += x;
promise( _credit = old_credit + x );
promise( balance() == old_balance + x && invariant() );
}
protected:
bool invariant() {
return _credit <= _maxcredit && account::invariant();
}
void hold() { old_credit = _credit; account::hold(); }
private:
float _maxcredit, _credit;
float old_credit;
};
Taking our view of a person as an actor as a starting point, we need first to establish the repertoire of possible behavior.
enum Role { Person = 0 , Student, Employer, Final };
class actor { defines the repertoire
public:
actor() { }
virtual void walk() { if (exists()) self()->walk(); }
virtual void talk() { if (exists()) self()->talk(); }
virtual void think() { if (exists()) self()->think(); }
virtual void act() { if (exists()) self()->act(); }
virtual void become(Role) { } only for a person
virtual void become(actor*) { }
virtual actor* self() { return this; } an empty self
int exists() { return self() != this; } who ami
};
Apart from the repertoire of possible behavior,
which consists of the ability to walk, talk,
think and act,
an actor has the ability to establish its own
identity (self) and to check whether it exists
as an actor, which is true only if it has become another self.
However, an actor is not able to assume a different role
or to become another self. We need a person for that!
Next, we may wish to refine the behavior of an actor for certain roles, such as for example the student and employer roles, which are among the many roles a person can play.
class student : public actor {
public:
void talk() { cout << "OOP" << endl; }
void think() { cout << "Z" << endl; }
};
class employer : public actor {
public:
void talk() { cout << "$$" << endl; }
void act() { cout << "business" << endl; }
};
Only a person has the ability
to assume a different role or to assume a different identity.
Apart from becoming a Student or Employer,
a person may for example become an adult_person
and in that capacity again assume a variety of roles.
class person : public actor {
public:
person(); to create a person
void become(Role r); to become a ...
void become(actor* p); change identity
int exists() { return role[Person] != this; }
actor* self() { return exists()?role[Person]->self():role [ role] ; }
private:
int _role;
actor* role[Final+1]; the repertoire
};
A person may check whether he exists as a Person,
that is whether the Person role differs from the person's
own identity.
A person's self may be characterized as the actor
belonging to the role the person is playing,
taking a possible change of identity into account.
When a person is created, his repertoire is still empy.
person::person() {
for (int i = Person; i <= Final ; i++ ) role[i] = this;
become( Person );
}
Only when a person changes identity by becoming
a different actor (or person) or by assuming
one of his (fixed) roles, he is capable of displaying
actual behavior.
void person::become(actor* p) { role[Person] = p; } permanent
void person::become(Role r) {
require( Person <= r && r <= Final );
if (exists()) self()->become(r);
else {
_role = r;
if ( role [ role] == this ) {
switch (_role) {
case Person: break; nothing changes
case Student: role [ role] = new student; break;
case Employer: role [ role] = new employer; break;
case Final: role [ role] = new actor; break;
};
}
}
}
Assuming or 'becoming' a role results in creating
a role instance if none exists and setting the _role
instance variable to that particular role.
When a person's identity has been changed,
assuming a role takes effect for the actor that
replaced the person's original identity.
(However, only a person can change roles!)
The ability to become an actor allows us to model
the various phases of a person's lifetime
by different classes, as illustrated by
the adult_person class.
class adult_person : public person {
public:
void talk() { cout << "interesting" << endl; }
};
In the example code below we have a person
talking while assuming different roles.
Note that the person's identity may be restored
by letting the person become its original self.
The dynamic role switching pattern can be used in any situation where we wish to change the functionality of an object dynamically. It may for example be used to incorporate a variety of tools in a drawing editor, as illustrated inperson p; p.talk();empty
p.become(Student); p.talk();OOP
p.become(Employer); p.talk();$$
p.become(new adult_person); p.talk();interesting
p.become(Student); p.talk();OOP (new student)
p.become(&p); p.talk();$$ (old role)
p.become(Person); // initial state
The Model-View or Observer pattern
class event {
public:
virtual void operator()() {} activate
void dependent(event* e);
void update(); to process dependent events
private:
set _dep; initially empty
};
The event class
must provide for a function to activate the event,
and in addition it must allow for defining dependent
events, that may in turn be activated by calling update.
In the example below, which is adapted from
class thermometer {
friend class Reset;
public:
virtual void set(float v);
virtual float get();
protected:
float _temp; absolute temperature
};
The thermometer keeps its value in degrees Kelvin,
and may be refined into a Centigrade and Fahrenheit
thermometer using standard conversion rules.
For realizing a user interface, that allows us to set and display the value of a thermometer, we need classes like prompter and displayer:
class prompter : public widget {
public:
prompter(char* msg);
float get();
};
class displayer : public widget {
public:
displayer(char* msg);
void put( float v);
};
These classes may be refined to allow for
textual as well as graphical input and display.
To manage temperature updates, we introduce the Get, Reset and Show events:
class Get : public event {
public:
Get(thermometer* th, prompter* p ) : _th(th), _p(p) {}
void operator()() {
_th->set ( _p->get() );
event::update();
}
private:
thermometer* _th; prompter* _p;
};
Activating the Get event results in displaying
a prompter and setting the thermometer's value
with the value obtained from it.
Then its dependent events are activated.
Reset events are needed to update the value of other thermometers. To allow the Reset event access to the stored temperature value, the class Reset has been made a friend of thermometer. Alternatively, the _temp instance variable could have been made a static (class) variable.
The Show event simply puts the value of the thermometer in a displayer. In it defined in a similar way as the Get events.
When a Get event occurs for a particular thermometer, first the other thermometers must be reset, and then the displays for the various thermometers must be changed accordingly. That means that we must create at least one Get event for each thermometer, and sufficient Reset events to update the value of every other thermometer. Also, we must have one or more Show events for each thermometer. Both the appropriate Reset and Show events must be declared to be dependent upon the occurrence of a Get event. Both Get and Show events may be made available as entries in a menu. When defining dependencies take care not to create cycles. Alternatively you may check dynamically for cycles.