Chapter 4
IN THIS CHAPTER
Understanding what design patterns are and how you can use them
Implementing an observer pattern
Building a mediator pattern
When you work as a developer, eventually you start to notice that you do certain things repeatedly. For example, when you need to keep track of how many instances of a certain class you create, you define a static property called something like int InstanceCount;
, include a line that increments InstanceCount
in the constructor, and include a line that decrements InstanceCount
in the destructor. You make InstanceCount
private and include a static method that retrieves the value, such as getInstanceCount()
.
Because you use it so often, it becomes a pattern. The first time you used it, you had to think about it — how to design and implement it. Now, you barely have to think about it; you just do it. Thus, it’s a design pattern that you use.
This chapter takes a practical look at design patterns that you use when creating applications. It helps you understand why using design patterns reduces development time, makes code less error prone, and improves application efficiency. The chapter delves just a bit into history and usage, with the usage considerations relying on the singleton pattern as a starting point.
You also see how to create and use two common design patterns: observer and mediator. You may or may not actually use these patterns in your applications, but by seeing how they’re put together, you can find or create other patterns that will make your development process easier.
Way back in 1995, a book became an instant bestseller in the computer programming world: Design Patterns: Elements of Reusable Object-Oriented Software, by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. The four authors of this groundbreaking book would become known in the field of programming as The Gang of Four.
The Gang of Four drew on a body of knowledge in the field of architecture — not software architecture, but rather the field of those people who build tall buildings, brick-and-mortar style. That kind of architecture has been around for at least two-and-a-half centuries, so the field is more mature than the field of software development. And, in the field of building design, people have come up with common ways to design and build buildings and towns, without having to start over from scratch every time with a new set of designs. Christopher Alexander wrote a book in 1977 called A Pattern Language (see http://www.patternlanguage.com/ca/ca.html
for details), which teaches the major concepts of building architecture by using patterns. The Gang of Four drew on this knowledge and applied it to software development principles.
In their book, The Gang of Four point out something that seems obvious in hindsight (but then again, great discoveries are often deceptively simple): The best software developers reuse techniques in the sense of patterns. The description of the class that keeps an instance count is an example of a technique that can be used over and over.
Now, if you heavily explore the field of object-oriented programming (and computer science in general, really), you often see the term reusable. One of the goals of object-oriented programming is to make code reusable by putting it in classes. You then derive your own classes from these classes, thereby reusing the code in the base classes.
You could probably put an instance-counting class in a base class and always derive from it. But for some other designs, using a base class doesn’t always work. Instead, software developers apply the same design to a new set of classes. The design is reused, but not the code. That’s the idea behind design patterns. You don’t just write up your design patterns and stuff them into a bunch of base classes. Instead, you know the patterns. Or you keep a list or catalog of them. You can find a list of the seven most important design patterns at https://medium.com/educative/the-7-most-important-software-design-patterns-d60e546afb0e
.
In this section, you discover how to create a design pattern so that you can see what it is and, more important, how you can use it. You look at a situation in which you need a class that allows only one instance to exist at any given time. You’ve come across this need many times. For example, you may have a class that represents the computer itself. You want only one instance of it. You also may have a class that represents the planet Earth. Again, you need only one instance. If people try to create a second instance of the class in their code, they will receive a compiler error. The following sections discuss how to perform this task in C++ using the singleton pattern (which ensures that only one instance exists at a time).
You could spend a couple hours coming up with an approach to the problem of creating only a single instance. Or you could look at a pattern that already exists somewhere, such as what this section shows you.
To understand how to create the singleton pattern, you need to first understand an unusual concept that many C++ programmers don’t usually consider: You can make a constructor of a class private or protected, which prevents someone from directly creating an instance of a class. To make this process work, you include a static method that creates the instance for you. Static methods don’t have an instance associated with them. You call them directly by giving the class name, two colons, and the method name. Fortunately, the static method is a member of the class, so it can call the constructor and create an instance for you.
Here’s how you make a singleton class: First, make the constructor private. Next, add a public static method that does the following:
Finally, where do you store this single instance? You store its pointer in a static property of the class. Because it’s static, only one property is shared throughout the class, rather than a separate variable for each class instance. Also, make the variable private so that users can’t just modify it at will.
And, voilà — you have a singleton class! Here’s how it works: Whenever you need the single instance of the class, you don’t try to create it. (You’ll get a compile error! Yes, the compiler itself won’t let you do it.) Instead, you call the static method.
It’s time to see the singleton pattern at work. Listing 4-1 from the Singleton
example shows how to create such a class:
LISTING 4-1: Creating a Singleton Class
class Planet {
private:
static Planet *inst;
Planet() {}
~Planet() {}
public:
static Planet *GetInstance();
};
Planet *Planet::inst = 0;
Planet *Planet::GetInstance() {
if (inst == 0) {
inst = new Planet();
}
return inst;
}
int main() {
Planet *MyPlanet = Planet::GetInstance();
cout << "MyPlanet address: " << MyPlanet << endl;
Planet *MyPlanet2 = Planet::GetInstance();
cout << "MyPlanet2 address: " << MyPlanet2 << endl;
return 0;
}
To use this class, you can’t create an instance directly. Instead, you call the GetInstance()
method:
Planet *MyPlanet = Planet::GetInstance();
MyPlanet address: 0x3faf08
MyPlanet2 address: 0x3faf08
Look at the constructor: It’s private. Therefore, if you attempt something like this somewhere outside the class (such as in main()
):
Planet MyPlanet;
you get a compiler error. In Code::Blocks, you get this error:
error: 'Planet::Planet()' is private
error: within this context
Or if you try to create a pointer, you get the same error when you call new
:
Planet *MyPlanet = new Planet();
The singleton pattern is about creating and destroying a single instance as needed, so you don’t want anything deleting the instance that the application creates. Just as you would make the constructor private, you would also make the destructor private, as shown in Listing 4-1. If you try to delete an instance after you obtain it, as in
Planet *MyPlanet = Planet::GetInstance();
delete MyPlanet;
then once again you receive an error message — this time, for the destructor:
error: 'Planet::~Planet()' is private
error: within this context
class Planet
{
private:
static Planet *inst;
Planet(string name)
{
cout << "Welcome to " << name << endl;
}
~Planet() {}
public:
static Planet *GetInstance(string name);
};
and your GetInstance()
method has this code in it:
Planet *Planet::GetInstance(string name)
{
if (inst == 0)
{
inst = new Planet(name);
}
return inst;
}
and you make two calls like this:
Planet *MyPlanet = Planet::GetInstance("Earth");
Planet *MyPlanet2 = Planet::GetInstance("Venus");
the results may not be as you expect. You end up with only one instance, which gets created with the first line — the one with "Earth"
passed in. In your second call to the GetInstance()
method, GetInstance()
sees that an instance already exists and does not even use the "Venus"
parameter. So be careful if you’re using parameters in constructors.
A common task in computer programming is when one or more instances of a class (or different classes) need to keep an eye on a certain object and perform various actions when that object changes. In other words, the class that keeps an eye on the others is an observer, hence the name of the pattern. The following sections tell you about the observer pattern and describe its use.
You may write an application that monitors various activities around your house when you’re away. Your application could be configurable; you could set it up so that the user can choose various actions to take if something goes awry. You might have the following options:
Each of these actions can exist in a different class, each with its own code for handling the situation. The one about saving a note to a file is easy: You would open a file, write to it, and close the file. The email example might involve obtaining a Simple Mail Transfer Protocol (SMTP) library, using it to create a message object, and then sending the message. To notify the police, your computer would have to be hooked up to an online security system that’s accessible via the phone lines or perhaps via the Internet, and the police would need a similar system at their end. The class for this would send a signal over the lines to the police, much like the way a secret button that notifies the police of a robbery at a gas station works. Finally, you might have a similar contraption hooked up to the brain of your little robotic watchdog, Fido; after receiving a high-voltage jolt, Fido can go on high alert and ward off the intruders. These situations use Observer
classes (each class derives from a base class called Observer
).
Now, you would also have a class whose object detects the problem in the house. This object might be hooked up to an elaborate security system, and when the change takes place, the computer calls a method inside this object. We call this class the Subject
class. So think about what is happening here:
Subject
instance.Observer
classes have objects that watch the Subject
instance. The method in the Subject
class then calls methods in each of the Observer
objects. These methods take the appropriate action, whether it’s write to a file, notify the police, zap the robotic dog, or whatever.Here’s the catch: The people using your computer application can determine which Observer
classes they want to respond to the event (possibly, via an Options dialog box). The ability of the user to determine which Observer
classes to use means that the design must be flexible. In order to obtain this flexibility, you need to add the following requirement: You might add new Observer
classes as they come up, so the Subject
class must accommodate them all. One Observer
might signal a helicopter to fly in and chase a robber who’s making a getaway. But you can’t be sure what you’ll come up with next. All you know is that you may add Observer
subclasses and instances of these subclasses. Here are the issues that come up when designing such a set of classes:
Subject
class, and whenever an event takes place, the event handler calls a routine in all the Observer
instances. The Observer
instances then decide whether they want to use the information. The problem with this situation is that you have to call a method within the Observer
classes (call into the class), even if the individual instances don’t want the information.Observer
instance constantly check the Subject
instance, looking for an event. (This process is called polling.) The problem here is that this process can push the computer to its limits, believe it or not: If every single Observer
instance is constantly calling into the Subject
class, you’ll have a lot of activity going on for possibly hours on end, keeping the CPU nice and toasty. That’s not a good idea, either.Observer
class contains a method called Respond()
. Meanwhile, the Subject
class includes a list of Observer
instances. Further, the Subject
class includes a method called Event
, which the computer calls whenever something happens, such as a break-in. The application adds and removes Observer
instances to and from the Subject
’s list of Observer
instances, based on the options the people choose when using your application.As you can imagine, this is a recurring pattern that a lot of applications use. Although zapping a robotic dog might not be commonplace, other applications use this general model. For example, in some C++ editors, you can open the same document in multiple windows, all under one instance of the editor application. When you change the code in one window, you immediately see the change in the other windows. Each class probably has a window, and these windows are the Observer
classes. The Subject
represents the underlying document.
This section discusses how to create an Observer
class. The Observer
class contains a method called Respond()
, which is a purely abstract function in the class declaration — meaning that the derived classes must create their own version of the Respond()
function. It’s up to the derived classes to respond to the event in their own ways. The following lines from the AddRemoveItems
example (see Listing 4-2, later in the chapter) show how to create the Observer
class:
class Observer {
public:
virtual void Respond() = 0;
};
As you can see, there’s not much here, so the example adds some derived classes. Here are a couple:
class Dog : public Observer {
public:
void Respond();
};
class Police : public Observer {
protected:
string name;
public:
Police(string myname) { name = myname; }
void Respond();
};
And here are the Respond()
methods for these two classes. For now, to keep it simple, they just write something to the console:
void Dog::Respond() {
cout << "Bark bark" << endl;
}
void Police::Respond() {
cout << name << ": 'Drop the weapon! Now!'" << endl;
}
Again, so far, there’s nothing particularly interesting about this. These lines of code represent just a couple methods that do their thing, really. When the next step rolls around, though, things get exciting. Here’s the Subject
class:
class Subject {
protected:
int Count;
Observer *List[100];
public:
Subject() { Count = 0; }
void AddObserver(Observer *Item);
void RemoveObserver(Observer *Item);
void Event();
};
This class has a list of Observer
instances in its List
member. The Count
member is the number of items in the list. Two methods for adding and removing Observer
instances are available: AddObserver()
and RemoveObserver()
. A constructor initializes the list by just setting its count to 0, and there’s the Event()
method. Here’s the code for the AddObserver()
and RemoveObserver()
methods. These functions simply manipulate the arrays:
void Subject::AddObserver(Observer *Item) {
List[Count] = Item;
Count++;
}
void Subject::RemoveObserver(Observer *Item) {
int i;
bool found = false;
for (i=0; i < Count; i++) {
if (!found && List[i] == Item) {
found = true;
List[i] = List[i+1];
}
}
if (found) {
Count--;
}
}
The RemoveObserver()
function uses some little tricks (again, a pattern!) to remove the item. It searches through the list until it finds the item; after that, it continues through the list, pulling items back one slot in the array. And finally, if it finds the item, it decreases Count
by 1
. The Event()
method looks like this:
void Subject::Event() {
int i;
for (i=0; i < Count; i++) {
List[i]->Respond();
}
}
This code climbs through the list, calling Respond()
for each item in the list. When you put this all together, you can have a main()
that sets up these items. Here’s one possibility:
Dog Fido;
Police TJHooker("TJ");
Police JoeFriday("Joe");
Subject Alarm;
Alarm.AddObserver(&Fido);
Alarm.AddObserver(&TJHooker);
Alarm.AddObserver(&JoeFriday);
Alarm.RemoveObserver(&TJHooker);
Alarm.Event();
The code creates three Observer
instances (one dog and two cops) and a
instance called Subject
Alarm
. It then adds all three instances to the list; but then TJ Hooker backs out, so the code removes him from the list.
To test the additions, the code calls Event()
. (Normally you call Event()
when an actual break-in event occurs.) And when you run this code, you get the responses of each of the registered observers:
Bark bark
Joe: 'Drop the weapon! Now!'
Notice that the TJHooker
Observer
didn’t respond, because it isn’t in the list and didn’t receive a notification. It’s still an instance.
class Observer {
public:
virtual void Respond() {}
};
Then you can modify the Subject
class and its methods, like so:
class Subject {
protected:
list<Observer *> OList;
public:
void AddObserver(Observer *Item);
void RemoveObserver(Observer *Item);
void Event();
};
void Subject::AddObserver(Observer *Item) {
OList.push_back(Item);
}
void Subject::RemoveObserver(Observer *Item) {
OList.remove(Item);
}
void Subject::Event() {
list<Observer *>::iterator iter;
for (iter=OList.begin(); iter!=OList.end(); iter++) {
Observer *item = (*iter);
item->Respond();
}
}
Note that the list saves pointers to Observer
; not the Observer
instances themselves. That’s because, by default, the list
class makes a copy of whatever you put in the array. If you put in an actual instance, the list
class will make a copy (which creates problems with derived classes because the list
copies only the object being stored as an Observer
instance, not a class derived from Observer
). With pointers, a copy of a pointer still points to the original object, and therefore the items in the list are the originals (at least their addresses are in the list). The list can also add and remove items without needing the program to loop through all the items, as occurs when using an array.
When you have an application that lets its users configure various observers, you may want to create and delete observers based on the configurations. In that case, it’s possible to add an Observer
to a Subject
’s list automatically when you create the Observer
, and to remove the Observer
from the list when you delete the Observer
. To do this, you can call the AddObserver()
method from within the constructor and call the RemoveObserver()
method from within the destructor.
To make this technique work, you need to tell the object who the Subject
is by passing the name as a parameter to the constructor. The following code does this. Note that you have to move the Subject
class above the Observer
class because the Observer
’s constructor and destructor call into Subject
. Also, note the AddObserver()
and RemoveObserver()
functions are protected. However, to allow the Observer
class to use these functions, you need to add the word friend
followed by the word Observer
in the Subject
class. The code for the complete AddRemoveItems
application is in Listing 4-2.
LISTING 4-2: Adding and Removing Items in the Constructor and Destructor
#include <iostream>
using namespace std;
class Observer;
class Subject {
friend class Observer;
protected:
int Count;
Observer *List[100];
void AddObserver(Observer *Item);
void RemoveObserver(Observer *Item);
public:
Subject() { Count = 0; }
void Event();
};
class Observer {
protected:
Subject *subj;
public:
virtual void Respond() = 0;
Observer(Subject *asubj) {
subj = asubj;
subj->AddObserver(this);
}
virtual ~Observer() { subj->RemoveObserver(this); }
};
class Dog : public Observer {
public:
void Respond();
Dog(Subject *asubj) : Observer(asubj) {}
};
class Police : public Observer {
protected:
string name;
public:
Police(Subject *asubj, string myname) :
Observer(asubj) {
name = myname; }
void Respond();
};
void Dog::Respond() {
cout << "Bark bark" << endl;
}
void Police::Respond() {
cout << name << ": 'Drop the weapon! Now!'" << endl;
}
void Subject::AddObserver(Observer *Item) {
List[Count] = Item;
Count++;
}
void Subject::RemoveObserver(Observer *Item) {
int i;
bool found = false;
for (i=0; i < Count; i++) {
if (!found && List[i] == Item) {
found = true;
List[i] = List[i+1];
}
}
if (found) {
Count--;
}
}
void Subject::Event() {
int i;
for (i=0; i < Count; i++) {
List[i]->Respond();
}
}
int main() {
Subject Alarm;
Police *TJHooker = new Police(&Alarm, "TJ");
cout << "TJ on the beat" << endl;
Alarm.Event();
cout << endl;
cout << "TJ off for the day" << endl;
delete TJHooker;
Alarm.Event();
return 0;
}
Notice the Dog(Subject *asubj) : Observer(asubj) {}
line of code in the listing. This line tells the application to call the base class constructor first with the subject. This action ensures that the base object, Observer
, is correctly instantiated before Dog
is instantiated. If you don’t do this, then the instantiation of Dog
will fail because Dog
won’t have access to the resources in the base class that it needs.
The idea behind the mediator pattern is that it performs the work of organizing class communication when you have classes that interact in a complex way. That way, only the underlying mediator class needs to know about all the instances. The instances themselves communicate only with the mediator. The following sections describe the basis for this pattern and demonstrate how it works.
Suppose that you’re designing a sophisticated, complex model of a car. You’re going to include the following parts, each of which will have its own class:
Part of your task is to model the behaviors that these classes provide:
This list represents nine objects interacting with each other in different ways. You could try to make all the objects communicate directly with each other. In the code, making them communicate would mean that most of the classes would have to contain references to objects of the other classes. That technique could get pretty confusing.
Figure 4-1 shows a hierarchy of the interactions between classes that demonstrates that you don’t have to have every class communication directly with every other class.
FIGURE 4-1: A model of the hierarchy between classes.
In the example, when there’s a hill, the road angle either increases or decreases, depending on the side of the hill you’re on (uphill or downhill). The road does not need to know about all the other car parts. Instead, it just informs the mediator of the change. The mediator then informs the necessary car parts.
This may seem like overkill because the car parts should be able to talk with each other directly. The idea is that if you enhance this application later, you may want to add more car parts. Rather than connecting the new car part to all the necessary existing car parts, you just make a connection with the mediator object. Suppose that you add a new part called an automatic transmission. When the car begins to climb a hill, the automatic transmission might detect the change in grade and automatically shift to a lower gear, resulting in an increase to the engine speed. To add this class, you only need to define its behavior and specify how it responds to various events, and then hook it up to the mediator. You also modify the mediator so it knows something about the automatic transmission’s behavior. Thus, you don’t need to hook it up to all the other instances. Figure 4-2 shows how the application classes look with the mediator in place.
FIGURE 4-2: A mediator certainly cleans things up!
One thing not shown in Figure 4-2 (for the purpose of avoiding clutter) is that all the various car parts (including the road!) derive from a base class called CarPart
. This class will have a single member: a pointer to a Mediator
instance. Each of the car parts, then, will inherit a pointer to the Mediator
instance.
The Mediator
class has a PartChanged()
method. This is the key function: Anytime any of the car parts experiences a change, it calls PartChanged()
. Remember that a car part can experience a change in only one of two ways: through an outside force unrelated to the existing classes (such as the driver pushing the gas pedal or turning the steering wheel) or through the Mediator
instance. If the change comes from the Mediator
instance, it was triggered through one of the other objects. Consider the following steps:
Engine
instance.Engine
instance changes its speed and then tells the Mediator
of the change.Mediator
instance knows which objects to notify of the change. For this change, it notifies the wheels to spin faster and the amount of electricity produced to increase.Here’s another possible sequence:
Road
instance. The hill has a 10 degree incline.Road
instance notifies Mediator
of the change.Mediator
instance handles this by figuring out how much to decelerate; it then notifies the wheels to slow down.So you can see that most of the application smarts are in the Mediator
class.
It’s time to put everything you’ve discovered into coded form. The following sections break the car example into manageable pieces, but you need all the pieces before running the example.
The CarParts
example begins in Listing 4-3. This is a header file that contains the class declarations for the car parts. Each class provides behaviors appropriate for that part, such as starting and stopping the engine.
LISTING 4-3: Using the carparts.h File
#ifndef CARPARTS_H_INCLUDED
#define CARPARTS_H_INCLUDED
#include "mediator.h"
class CarControls; // forward reference
class CarPart {
protected:
Mediator *mediator;
CarPart(Mediator *med) : mediator(med) {}
void Changed();
};
class Engine : public CarPart {
protected:
friend class Mediator; friend class CarControls;
int RPM;
int Revamount;
public:
Engine(Mediator *med) : CarPart(med),
RPM(0), Revamount(0) {}
void Start();
void PushGasPedal(int amount);
void ReleaseGasPedal(int amount);
void Stop();
};
class Electric : public CarPart {
protected:
friend class Mediator; friend class CarControls;
int Output;
int ChangedBy;
public:
Electric(Mediator *med) : CarPart(med),
Output(0), ChangedBy(0) {}
void ChangeOutputBy(int amount);
};
class Radio : public CarPart {
protected:
friend class Mediator; friend class CarControls;
int Volume;
public:
Radio(Mediator *med) : CarPart(med), Volume(0) {}
void AdjustVolume(int amount) { Volume += amount; }
void SetVolume(int amount) { Volume = amount; }
int GetVolume() { return Volume; }
};
class Wheels : public CarPart {
protected:
friend class Mediator; friend class CarControls;
int Speed;
public:
Wheels(Mediator *med) : CarPart(med), Speed(0) {}
int GetSpeed() { return Speed; }
void Accelerate(int amount);
void Decelerate(int amount);
};
class Brakes : public CarPart {
protected:
friend class Mediator; friend class CarControls;
int Pressure;
public:
Brakes(Mediator *med) : CarPart(med), Pressure(0) {}
void Apply(int amount);
};
class Headlights : public CarPart {
protected:
friend class Mediator; friend class CarControls;
int Brightness;
public:
Headlights(Mediator *med):CarPart(med), Brightness(0) {}
void TurnOn() { Brightness = 100; }
void TurnOff() { Brightness = 0; }
void Adjust(int Amount);
int GetBrightness() { return Brightness; }
};
class AirConditioner : public CarPart {
protected:
friend class Mediator; friend class CarControls;
int Level;
int ChangedBy;
public:
AirConditioner(Mediator *med) : CarPart(med),
Level(0), ChangedBy(0) {}
void TurnOn();
void TurnOff();
bool GetLevel() { return Level; }
void SetLevel(int level);
};
class Road : public CarPart {
protected:
friend class Mediator; friend class CarControls;
int ClimbAngle;
int BumpHeight;
int BumpWhichTire;
public:
Road(Mediator *med) : CarPart(med) {}
void ClimbDescend(int angle);
void Bump(int height, int which);
};
#endif // CARPARTS_H_INCLUDED
These classes know little of each other. That’s a good thing. However, they do know all about the mediator, which is fine. This example uses an important small feature of the American National Standards Institute (ANSI) version of C++. Notice the constructor line in the Engine
class definition:
Engine(Mediator *med) : CarPart(med),
RPM(0), Revamount(0) {}
After the constructor definition, you see a colon and the name of the base class, CarPart
. This calls the base class constructor. Then there’s a comma and the name of a property (RPM
) and a value in parentheses, which together form an initializer. When you create an instance of Engine
, the RPM
variable will get set to 0
. Further, the Revamount
variable will also get set to 0
. Using the constructor with an initializer causes the constructor to behave just like this code:
Engine(Mediator *med) {
RPM = 0;
Revamount = 0;
}
In Listing 4-4 you see the header file for the mediator along with a special class called CarControls
, which provides a central place through which you can control the car. You may have noticed the CarControls
friend class accesses the car parts in carparts.h
. This file includes several forward declarations and it knows about the various CarParts
classes. This file also includes a Mediator
derived class that provides a general interface to the whole system.
LISTING 4-4: Using the mediator.h File
#ifndef MEDIATOR_H_INCLUDED
#define MEDIATOR_H_INCLUDED
// Define all of the required forward references.
class CarPart;
class Engine;
class Electric;
class Radio;
class SteeringWheel;
class Wheels;
class Brakes;
class Headlights;
class AirConditioner;
class Road;
class Mediator {
public:
Engine *MyEngine;
Electric *MyElectric;
Radio *MyRadio;
SteeringWheel *MySteeringWheel;
Wheels *MyWheels;
Brakes *MyBrakes;
Headlights *MyHeadlights;
AirConditioner *MyAirConditioner;
Road *MyRoad;
Mediator();
void PartChanged(CarPart *part);
};
class CarControls : public Mediator {
public:
void StartCar();
void StopCar();
void PushGasPedal(int amount);
void ReleaseGasPedal(int amount);
void PressBrake(int amount);
void Turn(int amount);
void TurnOnRadio();
void TurnOffRadio();
void AdjustRadioVolume(int amount);
void TurnOnHeadlights();
void TurnOffHeadlights();
void ClimbHill(int angle);
void DescendHill(int angle);
void TurnOnAC();
void TurnOffAC();
void AdjustAC(int amount);
int GetSpeed();
CarControls() : Mediator() {}
};
#endif // MEDIATOR_H_INCLUDED
The methods for all the car parts appear in Listing 4-5. Note that these functions never call the functions in other car parts.
LISTING 4-5: Presenting the carparts.cpp File
#include <iostream>
#include "carparts.h"
using namespace std;
void CarPart::Changed() {
mediator->PartChanged(this);
}
void Engine::Start() {
RPM = 1000;
Changed();
}
void Engine::PushGasPedal(int amount) {
Revamount = amount;
RPM += Revamount;
Changed();
}
void Engine::ReleaseGasPedal(int amount) {
Revamount = amount;
RPM -= Revamount;
Changed();
}
void Engine::Stop() {
RPM = 0;
Revamount = 0;
Changed();
}
void Electric::ChangeOutputBy(int amount) {
Output += amount;
ChangedBy = amount;
Changed();
}
void Wheels::Accelerate(int amount) {
Speed += amount;
Changed();
}
void Wheels::Decelerate(int amount) {
Speed -= amount;
Changed();
}
void Brakes::Apply(int amount) {
Pressure = amount;
Changed();
}
void Headlights::Adjust(int Amount) {
Brightness += Amount;
}
void AirConditioner::TurnOn() {
ChangedBy = 100 - Level;
Level = 100;
Changed();
}
void AirConditioner::TurnOff() {
ChangedBy = 0 - Level;
Level = 0;
Changed();
}
void AirConditioner::SetLevel(int newlevel) {
Level = newlevel;
ChangedBy = newlevel - Level;
Changed();
}
void Road::ClimbDescend(int angle) {
ClimbAngle = angle;
Changed();
}
void Road::Bump(int height, int which) {
BumpHeight = height;
BumpWhichTire = which;
Changed();
}
Listing 4-6 contains the mediator source code and the source code for the CarControls
class. This code appears in mediator.cpp
.
LISTING 4-6: Presenting the mediator.cpp File
#include <iostream>
#include "carparts.h"
#include "mediator.h"
using namespace std;
Mediator::Mediator() {
MyEngine = new Engine(this);
MyElectric = new Electric(this);
MyRadio = new Radio(this);
MyWheels = new Wheels(this);
MyBrakes = new Brakes(this);
MyHeadlights = new Headlights(this);
MyAirConditioner = new AirConditioner(this);
MyRoad = new Road(this);
}
void Mediator::PartChanged(CarPart *part) {
if (part == MyEngine) {
if (MyEngine->RPM == 0) {
MyWheels->Speed = 0;
return;
}
if (MyEngine->Revamount == 0) {
return;
}
// If engine increases, increase the electric output
MyElectric->ChangeOutputBy(MyEngine->Revamount / 10);
if (MyEngine->Revamount > 0)
MyWheels->Accelerate(MyEngine->Revamount / 50);
}
else if (part == MyElectric) {
// Dim or brighten the headlights
if (MyHeadlights->Brightness > 0)
MyHeadlights->Adjust(MyElectric->ChangedBy / 20);
if (MyRadio->Volume > 0)
MyRadio->AdjustVolume(MyElectric->ChangedBy / 30);
}
else if (part == MyBrakes)
MyWheels->Decelerate(MyBrakes->Pressure / 5);
else if (part == MyAirConditioner)
MyElectric->ChangeOutputBy(
0 - MyAirConditioner->ChangedBy * 2);
else if (part == MyRoad) {
if (MyRoad->ClimbAngle > 0) {
MyWheels->Decelerate(MyRoad->ClimbAngle * 2);
MyRoad->ClimbAngle = 0;
}
else if (MyRoad->ClimbAngle < 0) {
MyWheels->Accelerate(MyRoad->ClimbAngle * -4);
MyRoad->ClimbAngle = 0;
}
}
}
void CarControls::StartCar() {
MyEngine->Start();
}
void CarControls::StopCar() {
MyEngine->Stop();
}
void CarControls::PushGasPedal(int amount) {
MyEngine->PushGasPedal(amount);
}
void CarControls::ReleaseGasPedal(int amount) {
MyEngine->ReleaseGasPedal(amount);
}
void CarControls::PressBrake(int amount) {
MyBrakes->Apply(amount);
}
void CarControls::TurnOnRadio() {
MyRadio->SetVolume(100);
}
void CarControls::TurnOffRadio() {
MyRadio->SetVolume(0);
}
void CarControls::AdjustRadioVolume(int amount) {
MyRadio->AdjustVolume(amount);
}
void CarControls::TurnOnHeadlights() {
MyHeadlights->TurnOn();
}
void CarControls::TurnOffHeadlights() {
MyHeadlights->TurnOff();
}
void CarControls::ClimbHill(int angle) {
MyRoad->ClimbDescend(angle);
}
void CarControls::DescendHill(int angle) {
MyRoad->ClimbDescend( 0 - angle );
}
int CarControls::GetSpeed() {
return MyWheels->Speed;
}
void CarControls::TurnOnAC() {
MyAirConditioner->TurnOn();
}
void CarControls::TurnOffAC() {
MyAirConditioner->TurnOff();
}
void CarControls::AdjustAC(int amount) {
MyAirConditioner->SetLevel(amount);
}
The CarControls
part runs a bit long, but it’s handy because it provides a central interface through which you can operate the car.
Now it’s finally time to try the mediator pattern by running the car through its paces. Listing 4-7 shows the various classes in action.
LISTING 4-7: Running the Car through Its Paces
#include <iostream>
#include "mediator.h"
#include "carparts.h"
using namespace std;
int main() {
// Create a new car.
Mediator *MyCar = new Mediator();
// Start the engine.
MyCar->MyEngine->Start();
cout << "Engine Started!" << endl;
// Accelerate.
MyCar->MyWheels->Accelerate(20);
cout << "The car is going: " <<
MyCar->MyWheels->GetSpeed() << endl;
// Apply the brakes.
MyCar->MyBrakes->Apply(20);
cout << "Applying the brakes." << endl;
cout << "The car is going: " <<
MyCar->MyWheels->GetSpeed() << endl;
// Stop the car.
MyCar->MyBrakes->Apply(80);
cout << "Applying the brakes." << endl;
cout << "The car is going: " <<
MyCar->MyWheels->GetSpeed() << endl;
// Shut off the engine.
MyCar->MyEngine->Stop();
cout << "Engine Stopped" << endl;
return 0;
}
The example code performs a few simple tasks using the various classes. You could always add more to your test code. The thing to notice is that everything goes through the Mediator class, MyCar
. Here’s the output from this example:
Engine Started!
The car is going: 20
Applying the brakes.
The car is going: 16
Applying the brakes.
The car is going: 0
Engine Stopped