DesignPatterns3

Design Patterns
Part III
(TIC++VII:C10)
Yingcai Xiao
07/08/08
Outline
What
For
How
Examples
Simplifying Idioms
Patterns to keep code simple and straightforward.
The Messenger Pattern
• Common task: to package information into an object.
• Use: to pass information around in one piece instead of in separate pieces.
• Implementation example: C10:MessengerDemo.cpp
class Point { // A messenger
public:
int x, y, z; // public for easier coding
Point(int xi, int yi, int zi) : x(xi), y(yi), z(zi) {} // init
Point(const Point& p) : x(p.x), y(p.y), z(p.z) {} // init
Point& operator=(const Point& rhs) { // assignment
x = rhs.x; y = rhs.y; z = rhs.z; return *this;
}
friend ostream& operator<<(ostream& os, const Point& p) {
return os << "x=" << p.x << " y=" << p.y << " z=" << p.z; // display
}
};
The Messenger Pattern
class Space {
public:
static Point translate(Point p, int dx, int dy, int dz) {
p.x += dx; p.y += dy; p.z += dz; return p;
}
};
int main() {
Point p1(1, 2, 3);
// a static method can be used without creating an object
Point p2 = Space::translate(p1, 3, 2, 1));
cout << "p1: " << p1 << " p2: " << p2 << endl;
}
The Collecting Parameter Pattern
• Common task: collecting parameter is messenger’s big brother, it
captures information from the function to which it is passed.
• Use: to collect information from multiple functions.
• Implementation example: C10:CollectingParameterDemo.cpp
The Collecting Parameter Pattern
class CollectingParameter : public vector<string> {}; // the class
class Filler {
public:
// the functions from which to collect information from
void f(CollectingParameter& cp) { cp.push_back("accumulating"); }
void g(CollectingParameter& cp) { cp.push_back("items"); }
void h(CollectingParameter& cp) { cp.push_back("as we go"); }
};
int main() {
Filler filler;
CollectingParameter cp;
filler.f(cp); filler.g(cp); filler.h(cp);
vector<string>::iterator it = cp.begin();
while(it != cp.end()) cout << *it++ << " ";
cout << endl;
} ///:~
The Singleton Pattern
• Common task: to allow one and only one instance of a class
• Use: to make sure there is no way to create more than one instance
of a special class for each application (e.g. the eAF - Application
Framework class in PA4. )
• Implementation example: C10:SingletonPattern.cpp
class Singleton {
static Singleton s;
// static: one copy per class, self declaration
int i;
// a single data member for demonstration
Singleton(int x) : i(x) { }
Singleton& operator=(Singleton&);
// Disallowed
Singleton(const Singleton&);
// Disallowed
public:
static Singleton& instance() { return s; } // return the reference to the sole instance
int getValue() { return i; } // access its
void setValue(int x) { i = x; }
};
The Singleton Pattern
// declaring s so that it can be referenced
Singleton Singleton::s(47);
int main() {
Singleton& s = Singleton::instance();
cout << s.getValue() << endl;
Singleton& s2 = Singleton::instance();
s2.setValue(9);
cout << s.getValue() << endl;
// the following will not compile
// Singleton *sp = new Singleton(); // no appropriate default constructor available
// Singleton *sp = new Singleton(8); //cannot access private member
// Singleton *sp = new Singleton(Singleton::instance()); // cannot access private
member
} ///:~
C10:SingletonPattern.cpp
The Singleton Pattern
• The key to creating a Singleton is to prevent the client programmer from
having any control over the lifetime of the object.
 declare all constructors private (note: a private constructor can’t be
used to create new objects, but can be used to declare an static object).
 prevent the compiler from implicitly generating any constructors
(compiler will not create a default construct if there is any kind of
constructors there).
 The copy constructor and assignment operator (which intentionally
have no implementations, since they will never be called) are declared
private to prevent any sort of copies being made.
• You must also decide how to create the single instance of the class. Here,
it’s created statically. Lazy initialization waits until the client programmer
asks for one and create it on demand. Tt only makes sense if it is expensive
to create your object, and if you don’t always need it.
• If you return a pointer instead of a reference, the user could inadvertently
delete the pointer, so the implementation above is considered safest (the
destructor can also be declared private or protected to alleviate that
problem).
The Singleton Pattern
• The object (static Singleton s) should be stored privately.
• You provide access through public member functions. Here, instance( )
produces a reference to the Singleton object. The rest of the interface
(getValue( ) and setValue( )) is the regular class interface.
Decoupling Event Handling with Command
• Common task: to separate event handling from “normal”
computation thread of the application.
• Use: to make sure event handling and the normal computation task
are separated so that they can each reside in a separate execution
thread for concurrent processing.
• Implementation example: C10:MulticastCommand.cpp
To avoid coupling of code using the Command pattern. Each
“normal” operation must periodically call a function to check the state
of the events, but with the Command pattern these normal operations
don’t need to know anything about what they are checking, and thus
are decoupled from the event-handling code.
Decoupling Event Handling with Command
int main() {
// Randomize for firing button click event randomly.
srand(time(0));
// Declare event generators
Button b1("Button 1"), b2("Button 2"), b3("Button 3");
// Declare event handlers
CheckButton cb1(b1), cb2(b2), cb3(b3);
// Add event handlers to the TaskRunner’s list.
TaskRunner::add(cb1); TaskRunner::add(cb2); TaskRunner::add(cb3);
// start the loop to perform “normal” computational work.
cout << "Control-C to exit" << endl;
while(true) {
procedure1(); procedure2(); procedure3();
// events will be checked and processed within the procedures
// while they perform the “normal” computational work.
}
} ///:~
Decoupling Event Handling with Command
// The procedures to perform the “normal” computational work.
// These need to be occasionally "interrupted" in order to
// check the state of the buttons or other events:
void procedure1() {
// Perform procedure1 operations here.
// ...
TaskRunner::run(); // Check all events
}
void procedure2() {
// Perform procedure2 operations here.
// ...
TaskRunner::run(); // Check all events
}
void procedure3() {
// Perform procedure3 operations here.
// ...
TaskRunner::run(); // Check all events
}
Decoupling Event Handling with Command
• The event generators are Button b1, b2, b3;
• The event handlers are CheckButton cb1, cb2, cb3;
• All event handlers are added to TaskRunner’s list.
• The main loop starts the procedures to perform the “normal”
computational work.
• The procedures will run the TaskRunner patriotically to check and
process events.
• The procedures knows how to perform “normal” computational
work, but does not know anything about how the events are
generated, checked or processed, therefore decoupled from event
handling.
Review: Command Pattern
• Common task: wrapping a function in an object => object-oriented command.
• AKS: functor, an object representing a function (event handler)
• Use: commands can be created, saved and passed around as objects with the
functions (handlers) encapsulated.
• Implementation example:
class Command {
public: virtual void execute() = 0; // can be any appropriate name
};
class CommandList{// An object that holds commands:
vector<Command*> commands;
public:
void add(Command* c) { commands.push_back(c); }
void run() {
vector<Command*>::iterator it = commands.begin();
while(it != commands.end()) (*it++)->execute();
}
};
Decoupling Event Handling with Command
class Task {
public: virtual void operation() = 0;
};
class TaskRunner {
static vector<Task*> tasks;
TaskRunner() {} // Make it a Singleton by privatized the constructors
TaskRunner& operator=(TaskRunner&); // Disallowed
TaskRunner(const TaskRunner&); // Disallowed
static TaskRunner tr; // the single instance of the class
public:
static void add(Task& t) { tasks.push_back(&t); }
static void run() {
vector<Task*>::iterator it = tasks.begin();
while(it != tasks.end()) (*it++)->operation();
}
};
Decoupling Event Handling with Command
// Declare all static objects for later reference
TaskRunner TaskRunner::tr;
vector<Task*> TaskRunner::tasks;
class EventSimulator {
clock_t creation;
clock_t delay;
public:
EventSimulator() : creation(clock()) {
delay = CLOCKS_PER_SEC/4 * (rand() % 20 + 1);
cout << "delay = " << delay << endl;
}
bool fired() {
return clock() > creation + delay;
}
};
Decoupling Event Handling with Command
// Something that can produce asynchronous events:
class Button {
bool pressed;
string id;
EventSimulator e; // For demonstration
public:
Button(string name) : pressed(false), id(name) {}
void press() { pressed = true; }
bool isPressed() {
if(e.fired()) press(); // Simulate the event
return pressed;
}
friend ostream&
operator<<(ostream& os, const Button& b) {
return os << b.id;
}
};
Decoupling Event Handling with Command
// The Command object
class CheckButton : public Task {
Button& button;
bool handled;
public:
CheckButton(Button & b) : button(b), handled(false) {}
void operation() {
if(button.isPressed() && !handled) {
cout << button << " pressed" << endl;
handled = true;
}
}
};
Decoupling Event Handling with Command
• The event handlers are defined as Command objects
(functors) following the Command pattern. This is an
object-oriented representation of event handlers.
• The Command class is Task here.
• CheckButtons are event handlers implementing the
Task/Command.
• The CommandList class is TaskRunner here.
• All event handlers are added to TaskRunner’s list.
• TaskRunner is a Signleton, we only need one
TaskRunner.
• When TaskRunner runs, it invokes “operation” for all
CheckButtons (i.e. the event handlers.)
Decoupling Event Handling with Command
•EventSimulator creates a random delay time, and
changes fired() from returning false to true when the delay
time has passed (“event has been fired”).
• EventSimulator objects are used inside Buttons to
simulate the act of a user event occurring at some
unpredictable time.
• TaskRunner is run periodically by all the “normal” code
in the program (procedure1( ), procedure2( ) and
procedure3( )).