Follow Techotopia on Twitter

On-line Guides
All Guides
eBook Store
iOS / Android
Linux for Beginners
Office Productivity
Linux Installation
Linux Security
Linux Utilities
Linux Virtualization
Linux Kernel
System/Network Admin
Programming
Scripting Languages
Development Tools
Web Development
GUI Toolkits/Desktop
Databases
Mail Systems
openSolaris
Eclipse Documentation
Techotopia.com
Virtuatopia.com
Answertopia.com

How To Guides
Virtualization
General System Admin
Linux Security
Linux Filesystems
Web Servers
Graphics & Desktop
PC Hardware
Windows
Problem Solutions
Privacy Policy

  




 

 

Thinking in C++ Vol 2 - Practical Programming
Prev Home Next

Decoupling event handling with Command

As you shall see in the next chapter, one of the reasons for employing concurrency techniques is to more easily manage event-driven programming, where the events can appear unpredictably in your program. For example, a user pressing a quit button while you re performing an operation expects the program to respond quickly.

An argument for using concurrency is that it prevents coupling across the pieces of your code. That is, if you re running a separate thread to watch the quit button, your program s normal operations don t need to know about the quit button or any of the other operations that need to be watched.

However, once you understand that coupling is the issue, you can avoid it 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:

//: C10:MulticastCommand.cpp {RunByHand}
// Decoupling event management with the Command pattern.
#include <iostream>
#include <vector>
#include <string>
#include <ctime>
#include <cstdlib>
using namespace std;
 
// Framework for running tasks:
class Task {
public:
virtual void operation() = 0;
};
 
class TaskRunner {
static vector<Task*> tasks;
TaskRunner() {} // Make it a Singleton
TaskRunner& operator=(TaskRunner&); // Disallowed
TaskRunner(const TaskRunner&); // Disallowed
static TaskRunner tr;
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();
}
};
 
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;
}
};
 
// 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;
}
};
 
// 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;
}
}
};
 
// The procedures that perform the main processing. 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
}
 
int main() {
srand(time(0)); // Randomize
Button b1("Button 1"), b2("Button 2"), b3("Button 3");
CheckButton cb1(b1), cb2(b2), cb3(b3);
TaskRunner::add(cb1);
TaskRunner::add(cb2);
TaskRunner::add(cb3);
cout << "Control-C to exit" << endl;
while(true) {
procedure1();
procedure2();
procedure3();
}
} ///:~
 

Here, the Command object is represented by Tasks executed by the Singleton TaskRunner. EventSimulator creates a random delay time, so if you periodically call fired( ) the result will change from false to true at some random time. EventSimulator objects are used inside Buttons to simulate the act of a user event occurring at some unpredictable time. CheckButton is the implementation of the Task that is periodically checked by all the normal code in the program you can see this happening at the end of procedure1( ), procedure2( ) and procedure3( ).

Although this requires a little bit of extra thought to set up, you ll see in Chapter 11 that threading requires a lot of thought and care to prevent the various difficulties inherent to concurrent programming, so the simpler solution may be preferable. You can also create a very simple threading scheme by moving the TaskRunner::run( ) calls into a multithreaded timer object. By doing this, you eliminate all coupling between the normal operations (procedures, in the above example) and the event code.

Thinking in C++ Vol 2 - Practical Programming
Prev Home Next

 
 
   Reproduced courtesy of Bruce Eckel, MindView, Inc. Design by Interspire