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.
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!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
};
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.
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 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; } };
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.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
};
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.person::person() { for (int i = Person; i <= Final ; i++ ) role[i] = this; become( Person ); }
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.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; }; } } }
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.class adult_person : public person { public: void talk() { cout << "interesting" << endl; } };
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
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.class event { public: virtual void operator()() {}activate
void dependent(event* e); void update();to process dependent events
private: set_dep; initially empty
};
In the example below, which is adapted from
The thermometer keeps its value in degrees Kelvin, and may be refined into a Centigrade and Fahrenheit thermometer using standard conversion rules.class thermometer { friend class Reset; public: virtual void set(float v); virtual float get(); protected: float _temp;absolute temperature
};
For realizing a user interface, that allows us to set and display the value of a thermometer, we need classes like prompter and displayer:
These classes may be refined to allow for textual as well as graphical input and display.class prompter : public widget { public: prompter(char* msg); float get(); }; class displayer : public widget { public: displayer(char* msg); void put( float v); };
To manage temperature updates, we introduce the Get, Reset and Show events:
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.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; };
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.