220 likes | 411 Views
Design Patterns Part III (TIC++VII:C10). Yingcai Xiao 07/08/08. What For How Examples. Outline. Simplifying Idioms Patterns to keep code simple and straightforward. Common task : to package information into an object.
E N D
Design Patterns Part III(TIC++VII:C10) Yingcai Xiao 07/08/08
What • For • How • Examples Outline
Simplifying IdiomsPatterns to keep code simple and straightforward.
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 Messenger 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 Collecting Parameter 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 } ///:~ The Singleton Pattern C10:SingletonPattern.cpp
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. The Singleton Pattern
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. // Theseneed 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. Decoupling Event Handling with Command
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(); • } • }; Review: Command Pattern
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( )). Decoupling Event Handling with Command