Chapter 29

Applying Design Patterns

WHAT’S IN THIS CHAPTER?

  • What a pattern is and what the difference is with a design technique
  • How to use the following patterns:
    • Iterator
    • Singleton
    • Factory
    • Proxy
    • Adapter
    • Decorator
    • Chain of Responsibility
    • Observer/Listener

A design pattern is a standard approach to program organization that solves a general problem. C++ is an object-oriented language, so the design patterns of interest to C++ programmers are generally object-oriented patterns, which describe strategies for organizing objects and object relationships in your programs. These patterns are usually applicable to any object-oriented language, such as for example C++, C#, Java, or Smalltalk. In fact, if you are familiar with C# or Java programming, you will recognize many of these patterns.

Design patterns are less language specific than are techniques. The difference between a pattern and a technique is admittedly fuzzy, and different books employ different definitions. This book defines a technique as a strategy particular to the C++ language, while a pattern is a more general strategy for object-oriented design applicable to any object-oriented language.

Note that many patterns have several different names. The distinctions between the patterns themselves can be somewhat vague, with different sources describing and categorizing them slightly differently. In fact, depending on the books or other sources you use, you may find the same name applied to different patterns. There is even disagreement as to which design approaches qualify as patterns. With a few exceptions, this book follows the terminology used in the seminal book Design Patterns: Elements of Reusable Object-Oriented Software, by Erich Gamma et al. (Addison-Wesley Professional, 1994) However, other pattern names and variations are noted when appropriate.

The design pattern concept is a simple, but powerful, idea. Once you are able to recognize the recurring object-oriented interactions that occur in a program, finding an elegant solution becomes a matter of merely selecting the appropriate pattern to apply. This chapter describes several design patterns in detail and presents sample implementations.

Certain patterns go by different names or are subject to different interpretations. Any aspect of design is likely to provoke debate among programmers, and the authors believe that is a good thing. Don’t simply accept these patterns as the only way to accomplish a task — draw on their approaches and ideas to refine them and form new patterns.

THE ITERATOR PATTERN

The iterator pattern provides a mechanism for separating algorithms or operations from the data on which they operate. At first glance, this pattern seems to contradict the fundamental principle in object-oriented programming of grouping together in objects data and the behaviors that operate on that data. While that argument is true on a certain level, the iterator pattern does not advocate removing fundamental behaviors from objects. Instead, it solves two problems that commonly arise with tight coupling of data and behaviors.

The first problem with tightly coupling data and behaviors is that it precludes generic algorithms that work on a variety of objects, not all of which are in the same class hierarchy. In order to write generic algorithms, you need some standard mechanism to access the contents of the objects.

The second problem with tightly coupled data and behaviors is that it’s sometimes difficult to add new behaviors. At the very least, you need access to the source code for the data objects. However, what if the object hierarchy of interest is part of a third-party framework or library that you cannot change? It would be nice to be able to add an algorithm or operation that works on the data without modifying the original object hierarchy of data.

You’ve already seen an example of the iterator pattern in the STL. Conceptually, iterators provide a mechanism for an operation or algorithm to access a container of elements in a sequence. The name comes from the English word iterate, which means “repeat.” It applies to iterators because they repeat the action of moving forward in the sequence to reach each new element. In the STL, the generic algorithms use iterators to access the elements of the containers on which they operate. By defining a standard iterator interface, the STL allows you to write algorithms that can work on any container that supplies an iterator with the appropriate interface. Thus, iterators allow you to write generic algorithms without modifying the classes that hold the data. Figure 29-1 shows an iterator as an assembly line that sends the elements of a data object to an “operation.”

Chapter 17 illustrates a detailed example on how to implement an iterator for a class that conforms to the STL requirements, which means that its iterator can be used by the generic STL algorithms.

THE SINGLETON PATTERN

The singleton is one of the simplest design patterns. In English the word “singleton” means “one of a kind” or “individual.” It has a similar meaning in programming. The singleton pattern is a strategy for enforcing the existence of exactly one instance of a class in a program. Applying the singleton pattern to a class guarantees that only one object of that class will ever be created. The singleton pattern also specifies that that one object is globally accessible from anywhere in the program. Programmers usually refer to a class following the singleton pattern as a singleton class.

If your program relies on the assumption that there will be exactly one instance of a class, you should enforce that assumption with the singleton pattern.

Example: A Logging Mechanism

Singletons are particularly useful for utility classes. Many applications have a notion of a logger — a class that is responsible for writing status information, debugging data, and errors to a central location. The ideal logging class has the following characteristics:

  • It is available at all times.
  • It is easy to use.
  • It provides a set of useful features.

The singleton pattern is a good match for a logger because, even though the logger could be used in many different contexts and for many different purposes, it is conceptually a single instance. Implementing the logger class as a singleton also makes it easier to use because you never have to worry about which logger is the current one or how to get a hold of the current logger; there’s only one, so it’s a moot point!

Implementation of a Singleton

There are two basic ways to implement a singleton in C++. The first approach uses static methods to form a class that needs no instantiation. The second uses access control levels to regulate the creation and access of one single instance.

Both approaches are shown here, using a simple Logger class as an example. This Logger class provides the following features:

  • It can log a single string or a vector of strings.
  • Each log message has an associated log level, which is prefixed to the log message.
  • Every log message is flushed to disk so that it will appear in the file immediately.

Static Class Singleton

Technically, a class that uses all static methods isn’t really a singleton: It’s a nothington, to coin a new term. The term singleton implies that there is exactly one instance of the class. If all of the methods are static and the class is never instantiated at all, can you call it a singleton? The authors claim that, because design patterns exist to help you build a mental model of object-oriented structures, you can call a static class a singleton if you please. However, you should recognize that a static class as a singleton lacks polymorphism and a built-in mechanism for construction and destruction. For cases like the Logger class, these may be acceptable losses.

The interface to the Logger static class follows. Note that it uses all static methods for access, so there is no need ever to instantiate a Logger object. In fact, the constructor has been made private to enforce this behavior.

image
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
// Definition of a singleton logger class, implemented with static methods.
class Logger
{
    public:
        static const std::string kLogLevelDebug;
        static const std::string kLogLevelInfo;
        static const std::string kLogLevelError;
        // Logs a single message at the given log level
        static void log(const std::string& inMessage, 
                        const std::string& inLogLevel);
        // Logs a vector of messages at the given log level
        static void log(const std::vector<std::string>& inMessages, 
                        const std::string& inLogLevel);
        // Closes the log file
        static void teardown();
    protected:
        static void init();
        static const char* const kLogFileName;
        static bool sInitialized;
        static std::ofstream sOutputStream;
    private:
        Logger() {}
};

Code snippet from StaticLoggerLogger.h

The implementation of the Logger class is fairly straightforward. The sInitialized static member is checked within each logging call to make sure that the init() method has been called to open the log file. Once the log file has been opened, each log message is written to it with the log level prepended.

image
#include <stdexcept>
#include "Logger.h"
using namespace std;
 
const string Logger::kLogLevelDebug = "DEBUG";
const string Logger::kLogLevelInfo = "INFO";
const string Logger::kLogLevelError = "ERROR";
const char* const Logger::kLogFileName = "log.out";
bool Logger::sInitialized = false;
ofstream Logger::sOutputStream;
 
void Logger::log(const string& inMessage, const string& inLogLevel)
{
    if (!sInitialized) {
        init();
    }
    // Print the message and flush the stream with endl.
    sOutputStream << inLogLevel << ": " << inMessage << endl;
}
void Logger::log(const vector<string>& inMessages, const string& inLogLevel)
{
    for (size_t i = 0; i < inMessages.size(); i++) {
        log(inMessages[i], inLogLevel);
    }
}
void Logger::teardown()
{
    if (sInitialized) {
        sOutputStream.close();
        sInitialized = false;
    }
}
void Logger::init()
{
    if (!sInitialized) {
        sOutputStream.open(kLogFileName, ios_base::app);
        if (!sOutputStream.good()) {
            throw runtime_error("Unable to initialize the Logger!");
        }
        sInitialized = true;
    }
}

Code snippet from StaticLoggerLogger.cpp

pen.gif

To focus on the actual singleton pattern, this implementation uses a hardcoded file name. Of course, in production quality software, this file name should be configurable by the user, and you should not use relative paths, but fully qualified paths, for example, by retrieving the temporary directory for your OS.

Access-Controlled Singleton

Object-oriented purists (Warning: They are out there, and they may work at your company!) might scoff at the static class solution to the singleton problem. Since you can’t instantiate a Logger object, you can’t build a hierarchy of loggers and make use of polymorphism. Such a hierarchy is rarely employed in the singleton case, but it is a valid drawback. Perhaps more significantly, as a result of using entirely static methods, there is no object orientation at all. This also means that there is no way to invoke a destructor. The static version of the Logger class requires the programmer to explicitly call the teardown() method. This is a serious defect. The class built in the previous example is essentially a collection of C-style functions, not a cohesive class.

To build a true singleton in C++, you can use the access control mechanisms as well as the static keyword. With this approach, an actual Logger object exists at run time, and the class enforces that exactly one exists. Clients can always get a hold of that object through a static method called instance(). The class definition looks like this:

image
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
// Definition of a true singleton logger class.
class Logger
{
    public:
        static const std::string kLogLevelDebug;
        static const std::string kLogLevelInfo;
        static const std::string kLogLevelError;
        // Returns a reference to the singleton Logger object
        static Logger& instance();
        // Logs a single message at the given log level
        void log(const std::string& inMessage, 
                 const std::string& inLogLevel);
        // Logs a vector of messages at the given log level
        void log(const std::vector<std::string>& inMessages, 
                 const std::string& inLogLevel);
    protected:
        // Static variable for the one-and-only instance  
        static Logger sInstance;
        // Constant for the filename
        static const char* const kLogFileName;
        // Data member for the output stream
        std::ofstream mOutputStream;
    private:
        Logger();
        virtual ~Logger();
};

Code snippet from SingletonLoggerLogger.h

One advantage of this approach is already apparent. Because an actual object will exist, the init() and teardown() methods present in the static solution can be omitted in favor of a constructor and destructor. This is a big win, because the previous solution required the client to explicitly call teardown() to close the file. Now that the logger is an object, the file can be closed when the object is destructed, which will happen when the program ends.

The implementation follows. Notice that the actual log() methods remain almost unchanged, except for the fact that they are no longer static, and that they no longer have to check if the logger has been initialized. The constructor and destructor are called automatically because the class contains an instance of itself as a static member. Because they are private, no external code can create or delete a Logger.

image
#include <stdexcept>
#include "Logger.h"
using namespace std;
 
const string Logger::kLogLevelDebug = "DEBUG";
const string Logger::kLogLevelInfo = "INFO";
const string Logger::kLogLevelError = "ERROR";
const char* const Logger::kLogFileName = "log.out";
// The static instance will be constructed when the program starts and
// destructed when it ends.
Logger Logger::sInstance;
 
Logger& Logger::instance()
{
    return sInstance;
}
Logger::~Logger()
{
    mOutputStream.close();
}
Logger::Logger()
{
    mOutputStream.open(kLogFileName, ios_base::app);
    if (!mOutputStream.good()) {
        throw runtime_error("Unable to initialize the Logger!");
    } 
}
void Logger::log(const string& inMessage, const string& inLogLevel)
{
    mOutputStream << inLogLevel << ": " << inMessage << endl;
}
void Logger::log(const vector<string>& inMessages, const string& inLogLevel)
{
    for (size_t i = 0; i < inMessages.size(); i++) {
        log(inMessages[i], inLogLevel);
    }
}

Code snippet from SingletonLoggerLogger.cpp

Using a Singleton

The following two pieces of code display the usage of the two different versions of the Logger class:

image
Logger::log("test message", Logger::kLogLevelDebug);
vector<string> items = {"item1", "item2"};
Logger::log(items, Logger::kLogLevelError);
Logger::teardown();

Code snippet from StaticLoggerTestStaticLogger.cpp

image
Logger::instance().log("test message", Logger::kLogLevelDebug);
vector<string> items = {"item1", "item2"};
Logger::instance().log(items, Logger::kLogLevelError);

Code snippet from SingletonLoggerTestTrueSingletonLogger.cpp

Both programs have the same functionality. After executing, the file log.out should contain the following lines:

DEBUG: test message
ERROR: item1
ERROR: item2

Singletons and Multithreading

The implementation of the singleton pattern in the previous section has a few issues. The first problem has to do with file static dependencies. The C++ standard does not define the order of initialization of static variables across translation units. Because of this, it can happen that some code is accessing the singleton class before it has been constructed, which leads to undefined behavior that is difficult to track. A second problem is that destruction of static objects is not guaranteed to occur in the order that you desire.

These problems can be solved by moving the static instance into the instance() method as follows:

Logger& Logger::instance()
{
    static Logger sInstance;
    return sInstance;
}

However, this solution is not safe in a multithreaded scenario, because there can be a race condition between the two lines in the new instance() method. To fix this race condition, the following implementation uses a static pInstance pointer to the single instance of the class. This static pointer will be initialized within a thread-safe block of code using a mutex for thread synchronization. The copy constructor and assignment operator are declared as private. The reason for the Cleanup class is discussed later in this section.

image
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <mutex>
// Definition of a multithread safe singleton logger class
class Logger
{
    public:
        static const std::string kLogLevelDebug;
        static const std::string kLogLevelInfo;
        static const std::string kLogLevelError;
        // Returns a reference to the singleton Logger object
        static Logger& instance();
        // Logs a single message at the given log level
        void log(const std::string& inMessage, 
                 const std::string& inLogLevel);
        // Logs a vector of messages at the given log level
        void log(const std::vector<std::string>& inMessages, 
                 const std::string& inLogLevel);
    protected:
        // Static variable for the one-and-only instance
        static Logger* pInstance;
        // Constant for the filename
        static const char* const kLogFileName;
        // Data member for the output stream
        std::ofstream mOutputStream;
        // Embedded class to make sure the single Logger
        // instance gets deleted on program shutdown.
        friend class Cleanup;
        class Cleanup
        {
           public:
               ~Cleanup();
        };
        // Logs message. The thread should own a lock on sMutex
        // before calling this function.
        void logHelper(const std::string& inMessage, 
                      const std::string& inLogLevel);
    private:
        Logger();
        virtual ~Logger();
        Logger(const Logger&);
        Logger& operator=(const Logger&);
        static std::mutex sMutex;
};

Code snippet from ThreadSafeLoggerLogger.h

The new implementation is as follows:

image
#include <stdexcept>
#include "Logger.h"
using namespace std;
 
const string Logger::kLogLevelDebug = "DEBUG";
const string Logger::kLogLevelInfo = "INFO";
const string Logger::kLogLevelError = "ERROR";
const char* const Logger::kLogFileName = "log.out";
Logger* Logger::pInstance = nullptr;
mutex Logger::sMutex;
 
Logger& Logger::instance()
{
    static Cleanup cleanup;
    lock_guard<mutex> guard(sMutex);
    if (pInstance == nullptr)
        pInstance = new Logger();
    return *pInstance;
}
Logger::Cleanup::~Cleanup()
{
    lock_guard<mutex> guard(Logger::sMutex);
    delete Logger::pInstance;
    Logger::pInstance = nullptr;
}
Logger::~Logger()
{
    mOutputStream.close();
}
Logger::Logger()
{
    mOutputStream.open(kLogFileName, ios_base::app);
    if (!mOutputStream.good()) {
        throw runtime_error("Unable to initialize the Logger!");
    } 
}
void Logger::log(const string& inMessage, const string& inLogLevel)
{
    lock_guard<mutex> guard(sMutex);
    logHelper(inMessage, inLogLevel);
}
void Logger::log(const vector<string>& inMessages, const string& inLogLevel)
{
    lock_guard<mutex> guard(sMutex);
    for (size_t i = 0; i < inMessages.size(); i++) {
        logHelper(inMessages[i], inLogLevel);
    }
}
void Logger::logHelper(const std::string& inMessage,
    const std::string& inLogLevel)
{
    mOutputStream << inLogLevel << ": " << inMessage << endl;
}

Code snippet from ThreadSafeLoggerLogger.cpp

The Cleanup class is there to make sure the single Logger instance gets deleted properly on program shutdown. This is necessary because this implementation is dynamically allocating the Logger instance by using the new operator in a block of code protected with a mutex. A static instance of the Cleanup class will be created the first time the instance() method is called. When the program terminates, the C++ runtime will destroy this static Cleanup instance, which will trigger the deletion of the Logger object and a call to the Logger destructor to close the file.

pen.gif

This version uses the C++11 threading library for its lock_guard and mutex classes, discussed in Chapter 22. If you do not have support for this C++11 threading library, you can replace those with any other kind of lock_guard and mutex available on your platform.

THE FACTORY PATTERN

A factory in real life constructs tangible objects, such as tables or cars. Similarly, a factory in object-oriented programming constructs objects. When you use factories in your program, portions of code that want to create a particular object ask the factory for an instance of the object instead of calling the object constructor themselves. For example, an interior decorating program might have a FurnitureFactory object. When part of the code needs a piece of furniture such as a table, it would call the createTable() method of the FurnitureFactory object, which would return a new table.

At first glance, factories seem to lead to complicated designs without clear benefits. It appears that you’re only adding another layer of complexity to the program. Instead of calling createTable() on a FurnitureFactory, you could simply create a new Table object directly. However, factories can actually be quite useful. Instead of creating various objects all over the program, you centralize the object creation for a particular domain. This localization is often a better model of real-world creation of objects.

Another benefit of factories is that you can use them alongside class hierarchies to construct objects without knowing their exact class. As you’ll see in the following example, factories can run parallel to class hierarchies.

The main benefit is that factories abstract the object creation process; you can easily substitute a different factory in your program. Just as you can use polymorphism with the created objects, you can use polymorphism with factories. The following example demonstrates this.

Example: A Car Factory Simulation

In the real world, when you talk about driving a car, you can do so without referring to the specific type of car. You could be discussing a Toyota or a Ford. It doesn’t matter, because both Toyotas and Fords are drivable. Now, suppose that you want a new car. You would then need to specify whether you wanted a Toyota or a Ford, right? Not always. You could just say “I want a car,” and depending on where you were, you would get a specific car. If you said, “I want a car” in a Toyota factory, chances are you’d get a Toyota. (Or you’d get arrested, depending on how you asked). If you said, “I want a car” in a Ford factory, you’d get a Ford.

The same concepts apply to C++ programming. The first concept, of a generic car that’s drivable, is nothing new; it’s standard polymorphism, described in Chapter 3. You could write an abstract Car class that defines a drive() method. Both Toyota and Ford could be subclasses of the Car class, as shown in Figure 29-2.

Your program could drive Cars without knowing whether they were really Toyotas or Fords. However, with standard object-oriented programming, the one place that you’d need to specify Toyota or Ford is when you create the car. Here, you would need to call the constructor for one or the other. You can’t just say, “I want a car.” However, suppose that you also had a parallel class hierarchy of car factories. The CarFactory superclass could define a virtual buildCar() method. The ToyotaFactory and FordFactory subclasses would override the buildCar() method to build a Toyota or a Ford. Figure 29-3 shows the CarFactory hierarchy.

Now, suppose that there is one CarFactory object in a program. When code in the program, such as a car dealer, wants a new car, it calls buildCar() on the CarFactory object. Depending on whether that car factory was really a ToyotaFactory or a FordFactory, the code would get either a Toyota or a Ford. Figure 29-4 shows the objects in a car dealer program using a ToyotaFactory.

Figure 29-5 shows the same program, but with a FordFactory instead of a ToyotaFactory. Note that the CarDealer object and its relationship with the factory stay the same.

This example demonstrates the main benefit: You can use polymorphism with factories. When you ask the car factory for a car, you might not know whether it’s a Toyota factory or a Ford factory, but either way it will give you a Car that you can drive. This approach leads to easily extensible programs; simply changing the factory instance can allow the program to work on a completely different set of objects and classes.

Implementation of a Factory

One reason for using factories is if the type of the object you want to create depends on some condition. For example, if you are a dealer who needs a car right away, you might want to put your order into the factory that has the fewest requests, regardless of whether the car you eventually get is a Toyota or a Ford. The following implementation shows how to write such factories in C++.

The first thing you’ll need is the hierarchy of cars. To keep this example simple, the Car class simply has an abstract method that returns a description of the car. Both Car subclasses are also defined in the following example:

image
#include <iostream>
class Car
{
    public:
        virtual void info() = 0;
};
class Ford : public Car
{
    public:
        virtual void info() { std::cout << "Ford" << std::endl; }
};
class Toyota : public Car
{
    public:
        virtual void info() { std::cout << "Toyota" << std::endl; }
};

Code snippet from CarFactoryCar.h

The CarFactory base class is a bit more interesting. Each factory keeps track of the number of cars in production. When the public requestCar() method is called, the number of cars in production at the factory is increased by one, and calling the pure virtual createCar() method returns a new car. The idea is that individual factories will override createCar() to return the appropriate type of car. The CarFactory itself implements requestCar(), which takes care of updating the number of cars in production. CarFactory also provides a public method to query the number of cars being produced at each factory.

The class definitions for the CarFactory class and subclasses are shown here:

image
#include "Car.h"
class CarFactory
{
    public:
        CarFactory();
        Car* requestCar();
        int getNumCarsInProduction() const;
    protected:
        virtual Car* createCar() = 0;
    private:
        int mNumCarsInProduction;
};
class FordFactory : public CarFactory
{
    protected:
        virtual Car* createCar();
};
class ToyotaFactory : public CarFactory
{
    protected:
        virtual Car* createCar();
};

Code snippet from CarFactoryCarFactory.h

As you can see, the subclasses simply override createCar() to return the specific type of car that they produce. The implementation of the CarFactory hierarchy is as follows:

image
#include "CarFactory.h"
// Initialize the count to zero when the factory is created.
CarFactory::CarFactory() : mNumCarsInProduction(0) {}
// Increment the number of cars in production and return the new car.
Car* CarFactory::requestCar()
{
    mNumCarsInProduction++;
    return createCar();
}
int CarFactory::getNumCarsInProduction() const
{
    return mNumCarsInProduction;
}
Car* FordFactory::createCar()
{
    return new Ford();
}
Car* ToyotaFactory::createCar()
{
    return new Toyota();
}

Code snippet from CarFactoryCarFactory.cpp

The implementation approach used in this example is called an abstract factory because the type of object created depends on which concrete subclass of the factory class is being used. A similar pattern can be implemented in a single class instead of a class hierarchy. In that case, a single create() method takes a type or string parameter from which it decides which object to create. For example, a CarFactory object would provide a buildCar() method that takes a string representing the type of car and constructs the appropriate type. The Microsoft MFC library uses such a concept. MFC has a function called CRuntimeClass::CreateObject() which accepts one parameter, a string which is the name of the class of which you want to create an object. The CreateObject() method is compiled into the MFC library in binary form, but still, due to the factory pattern, it is able to create objects of types unknown at the time the library was compiled.

pen.gif

Factory methods are one way to implement virtual constructors: methods that create objects of different types. For example, the buildCar() method creates both Toyotas and Fords, depending on the concrete factory object on which it is called.

Using a Factory

The simplest way to use a factory is to instantiate it and to call the appropriate method, as in the following piece of code:

ToyotaFactory myFactory;
Car* myCar = myFactory.requestCar();

A more interesting example makes use of the virtual constructor idea to build a car in the factory that has the fewest cars in production. To do this, you will need a function that looks at several factories and chooses the least busy one, such as the following function:

image
shared_ptr<CarFactory> getLeastBusyFactory(
    const vector<shared_ptr<CarFactory>>& inFactories)
{
    if (inFactories.size() == 0)
        return nullptr;
    shared_ptr<CarFactory> bestSoFar = inFactories[0];
    for (size_t i = 1; i < inFactories.size(); i++) {
        if (inFactories[i]->getNumCarsInProduction() <
            bestSoFar->getNumCarsInProduction()) {
            bestSoFar = inFactories[i];
        }
    }
    return bestSoFar;
}

Code snippet from CarFactoryCarTest.cpp

The following code makes use of this function to build 10 cars, whatever brand they might be, from the currently least busy factory.

image
vector<shared_ptr<CarFactory>> factories;
// Create 3 Ford factories and 1 Toyota factory. 
auto factory1 = make_shared<FordFactory>();
auto factory2 = make_shared<FordFactory>();
auto factory3 = make_shared<FordFactory>();
auto factory4 = make_shared<ToyotaFactory>();
// To get more interesting results, preorder some cars.
factory1->requestCar();
factory1->requestCar();
factory2->requestCar();
factory4->requestCar();
// Add the factories to a vector.
factories.push_back(factory1);
factories.push_back(factory2);
factories.push_back(factory3);
factories.push_back(factory4);
// Build 10 cars from the least busy factory.
for (size_t i = 0; i < 10; i++) {
    shared_ptr<CarFactory> currentFactory = getLeastBusyFactory(factories);
    shared_ptr<Car> theCar(currentFactory->requestCar());
    theCar->info();
}

Code snippet from CarFactoryCarTest.cpp

When executed, the program will print out the make of each car produced:

Ford
Ford
Ford
Toyota
Ford
Ford
Ford
Toyota
Ford
Ford

The results are rather predictable because the loop effectively iterates through the factories in a round-robin fashion. However, one could imagine a scenario where multiple dealers are requesting cars, and the current status of each factory isn’t quite so predictable.

Other Uses of Factories

You can also use the factory pattern for more than just modeling real-world factories. For example, consider a word processor in which you want to support documents in different languages, where each document uses a single language. There are many aspects of the word processor in which the choice of document language requires different support: the character set used in the document (whether or not accented characters are needed), the spellchecker, the thesaurus, and the way the document is displayed to name a few. You could use factories to design a clean word processor by writing an abstract LanguageFactory superclass and concrete factories for each language of interest, such as EnglishLanguageFactory and FrenchLanguageFactory. When the user specifies a language for a document, the program instantiates the appropriate language factory and attaches it to the document. From then on, the program doesn’t need to know which language is supported in the document. When it needs a language-specific piece of functionality, it can just ask the LanguageFactory. For example, when it needs a spellchecker, it can call the createSpellchecker() method on the factory, which will return a spellchecker in the appropriate language.

THE PROXY PATTERN

The proxy pattern is one of several patterns that divorce the abstraction of a class from its underlying representation. A proxy object serves as a stand-in for a real object. Such objects are generally used when using the real object would be time-consuming or impossible. For example, take a document editor. A document could contain several big objects, such as images. Instead of loading all those images when opening the document, the document editor could substitute all images with image proxies. These proxies don’t immediately load the images. Only when the user scrolls down in the document and reaches an image, the document editor will ask the image proxy to draw itself. Then, the proxy will delegate the work to the real image class, which will load the image.

Example: Hiding Network Connectivity Issues

Consider a networked game with a Player class that represents a person on the Internet who has joined the game. The Player class would include functionality that requires network connectivity, such as an instant messaging feature. In the event that a player’s connection becomes slow or unresponsive, the Player object representing that person can no longer receive instant messages.

Because you don’t want to expose network problems to the user, it may be desirable to have a separate class that hides the networked parts of a Player. This PlayerProxy object would substitute for the actual Player object. Clients of the class would either use the PlayerProxy class at all times as a gatekeeper to the real Player class, or the system would substitute a PlayerProxy when a Player became unavailable. During a network failure, the PlayerProxy object could still display the player’s name and last-known state, and could continue to function when the original Player object cannot. Thus, the proxy class hides some undesirable semantics of the underlying Player class.

Implementation of a Proxy

The public interface for a Player class follows. The sendInstantMessage() method requires network connectivity to properly function:

class Player
{
    public:
        virtual string getName();
        // Sends an instant message to the player over the network and
        // returns the reply as a string. Network connectivity is required.
        virtual string sendInstantMessage(const string& inMessage) const;
};

Proxy classes often evoke the is-a versus has-a debate. You could implement PlayerProxy as a completely separate class that contains a Player object. This design would make most sense if the PlayerProxy is always used by the program when it wants to talk to a Player object. Alternatively, you could implement PlayerProxy as a subclass that overrides functionality that requires network connectivity. This design makes it easy to swap out a Player for a PlayerProxy when network connectivity ceases. This example uses the latter approach by subclassing Player, as shown here:

class PlayerProxy : public Player
{
    public:
        virtual string sendInstantMessage(const string& inMessage) const;
};

The implementation of the PlayerProxy’s sendInstantMessage() method simply cuts out the network functionality and returns a string indicating that the player has gone offline.

string PlayerProxy::sendInstantMessage(const string& inMessage)
{
    return "The player could not be contacted.";
}

Another solution could be for the PlayerProxy’s sendInstantMessage() method to check the network connectivity, and either return a default string or forward the request. For example:

string PlayerProxy::sendInstantMessage(const string& inMessage)
{
    if (hasNetworkConnectivity())
        return Player::sendInstantMessage(inMessage);
    else
        return "The player could not be contacted.";
}

Using a Proxy

If a proxy is well written, using it should be no different from using any other object. For the PlayerProxy example, the code that uses the proxy could be completely unaware of its existence. The following function, designed to be called when the Player has won, could be dealing with an actual Player or a PlayerProxy. The code is able to handle both cases in the same way because the proxy ensures a valid result.

bool informWinner(const Player* inPlayer)
{
    string result;
    result = inPlayer->sendInstantMessage("You have won! Play again?");
    if (result == "yes") {
        cout << inPlayer->getName() << " wants to play again" << endl;
        return true;
    } else {
        // The player said no, or is offline.
        cout << inPlayer->getName() << " does not want to play again" << endl;
        return false;
    }
}

THE ADAPTER PATTERN

The motivation for changing the abstraction given by a class is not always driven by a desire to hide functionality or protect against performance concerns. Sometimes, the underlying abstraction cannot be changed but it doesn’t suit the current design. In this case, you can build an adapter or wrapper class. The adapter provides the abstraction that the rest of the code uses and serves as the bridge between the desired abstraction and the actual underlying code. Chapter 12 discusses how the STL uses the adapter pattern to implement containers like stack and queue in terms of other containers, such as deque and list.

Example: Adapting a Logger Class

For this adapter pattern example, let’s assume a very basic Logger class. Methods are shown with their implementations directly in the header file. This is not Best Practice, but is done to save space.

image
#include <string>
#include <iostream>
class Logger
{
    public:
        static const std::string kLogLevelDebug;
        static const std::string kLogLevelInfo;
        static const std::string kLogLevelError;
        Logger() { std::cout << "Logger constructor" << std::endl; }
        void log(const std::string& level, const std::string& str) {
            std::cout << level << ": " << str << std::endl;
        }
};
const std::string Logger::kLogLevelDebug = "DEBUG";
const std::string Logger::kLogLevelInfo = "INFO";
const std::string Logger::kLogLevelError = "ERROR";

Code snippet from LoggerAdapterLoggerAdapter.h

The Logger class has a constructor, which outputs a line of text to the standard console, and a method called log() that writes the given line of text to the console prefixed with a log level.

One reason why you might want to write a wrapper class around this basic Logger class is to change the interface of it. Maybe you are not interested in the log level and you would like to call the log() method with only one parameter, the message itself.

Implementation of an Adapter

The first step in implementing the adapter pattern is to define the new interface for the underlying functionality. This new interface is called NewLoggerInterface and looks as follows:

image
class NewLoggerInterface
{
    public:
        virtual void log(const std::string& str) = 0;
};

Code snippet from LoggerAdapterLoggerAdapter.h

This class is an abstract class, which declares the desired interface that you want for your new logger. It only defines one abstract method which needs to be implemented by any class inheriting from this interface. You can think of NewLoggerInterface as a mix-in class. Mix-in classes are discussed in Chapter 28.

The next step is to write the actual new logger class, NewLoggerAdapter, which inherits from the NewLoggerInterface so that it has the interface that you designed. It also privately inherits from the original Logger class. It is inherited privately so that no functionality from the original Logger class will be publicly available in the NewLoggerAdapter class. The constructor of the new class writes a line to standard output to keep track of which constructors are being called. The code then implements the abstract log() method from the NewLoggerInterface interface by forwarding the call to the original log() method and specifying kLogLevelInfo as log level:

image
class NewLoggerAdapter : public NewLoggerInterface, private Logger
{
    public:
        NewLoggerAdapter() {
            std::cout << "NewLoggerAdapter constructor" << std::endl;
        }
        virtual void log(const std::string& str) {
            Logger::log(Logger::kLogLevelInfo, str);
        }
};

Code snippet from LoggerAdapterLoggerAdapter.h

Using an Adapter

Since adapters exist to provide a more appropriate interface for the underlying functionality, their use should be straightforward and specific to the particular case. Given the previous example, the following program uses the new simplified interface for the Logger class:

image
#include "LoggerAdapter.h"
int main() 
{
    NewLoggerAdapter logger;
    logger.log("Testing the logger.");
    return 0;
} 

Code snippet from LoggerAdapterTestLoggerAdapter.cpp

When you compile and run this example, it will produce the following output:

Logger constructor
NewLoggerAdapter constructor
INFO: Testing the logger.

THE DECORATOR PATTERN

The decorator pattern is exactly what it sounds like — a “decoration” on an object. The pattern is used to change the behavior of an object at run time. Decorators are a lot like subclasses, but their effects can be temporary. For example, if you have a stream of data that you are parsing and you reach data that represents an image, you could temporarily decorate the stream object with an ImageStream object. The ImageStream constructor would take the stream object as a parameter and would have built-in knowledge of image parsing. Once the image is parsed, you could continue using the original object to parse the remainder of the stream. The ImageStream acts as a decorator because it adds new functionality (image parsing) to an existing object (a stream).

Example: Defining Styles in Web Pages

As you may already know, web pages are written in a simple text-based structure called HyperText Markup Language (HTML). In HTML, you can apply styles to a text by using style tags, such as <B> and </B> for bold and <I> and </I> for italic. The following line of HTML will display the message in bold:

<B>A party? For me? Thanks!</B>

The following line will display the message in bold italic:

<I><B>A party? For me? Thanks!</B></I>

Assume that you are writing an HTML editing application. Your users will be able to type in paragraphs of text and apply one or more styles to them. You could make each type of paragraph a new subclass, as shown in Figure 29-6, but that design could be cumbersome and would grow exponentially as new styles were added.

The alternative is to consider styled paragraphs not as types of paragraphs, but as decorated paragraphs. This leads to situations like the one shown in Figure 29-7, where an ItalicParagraph operates on a BoldParagraph, which in turn operates on a Paragraph. The recursive decoration of objects nests the styles in code just as they are nested in HTML.

Implementation of a Decorator

To decorate the Paragraph class with zero or more styles, you will need a hierarchy of styled Paragraph classes. Each of the styled Paragraph classes will be constructible from an existing Paragraph. This way, they can all decorate a Paragraph or a styled Paragraph. The most convenient way to implement the styled classes is as subclasses of Paragraph. Here is the Paragraph base class:

image
class Paragraph
{
    public:
        Paragraph(const string& inInitialText) : mText(inInitialText) {}
        virtual string getHTML() const { return mText; }
    protected:
        string mText;        
}; 

Code snippet from DecoratorDecorator.cpp

The BoldParagraph class will be a subclass of Paragraph so that it can override getHTML(). However, because we intend to use it as a decorator, its only public non-copy constructor takes a const reference to a Paragraph. Note that it passes an empty string to the Paragraph constructor because BoldParagraph doesn’t make use of the mText data member — its only purpose in subclassing Paragraph is to override getHTML().

image
class BoldParagraph : public Paragraph
{
    public:
        BoldParagraph(const Paragraph& inParagraph) :
            Paragraph(""), mWrapped(inParagraph) {}
        virtual string getHTML() const {
            return "<B>" + mWrapped.getHTML() + "</B>";
        }
    protected:
        const Paragraph& mWrapped;
};

Code snippet from DecoratorDecorator.cpp

The ItalicParagraph class is almost identical:

image
class ItalicParagraph : public Paragraph
{
    public:
        ItalicParagraph(const Paragraph& inParagraph) :
            Paragraph(""), mWrapped(inParagraph) {}
        virtual string getHTML() const {
            return "<I>" + mWrapped.getHTML() + "</I>";
        }
    protected:
        const Paragraph& mWrapped;
};

Code snippet from DecoratorDecorator.cpp

Again, remember that BoldParagraph and ItalicParagraph only subclass Paragraph so that they can override getHTML(). The content of the paragraph comes from the wrapped object, not from the mText data member.

Using a Decorator

From the user’s point of view, the decorator pattern is appealing because it is very easy to apply, and is transparent once applied. The client doesn’t need to know that a decorator has been employed at all. A BoldParagraph behaves just like a Paragraph.

Here is a quick example that creates and outputs a paragraph, first in bold, then in bold and italic:

image
Paragraph p("A party? For me? Thanks!");
// Bold
cout << BoldParagraph(p).getHTML() << endl;
// Bold and Italic
cout << ItalicParagraph(BoldParagraph(p)).getHTML() << endl; 

Code snippet from DecoratorDecorator.cpp

The output will be as follows:

<B>A party? For me? Thanks!</B>
<I><B>A party? For me? Thanks!</B></I>

There is an interesting side effect of this implementation that just happens to work correctly for HTML. If you applied the same style twice in a row, the effect would only occur once:

cout << BoldParagraph(BoldParagraph(p)).getHTML() << endl;

The result of this line is:

<B>A party? For me? Thanks!</B>

If you can see the reason why, you’ve mastered C++! What’s happening here is that instead of using the BoldParagraph constructor that takes a const Paragraph reference, the compiler is using the built-in copy constructor for BoldParagraph! In HTML, that’s fine — there’s no such thing as double-bold. However, other decorators built using a similar framework may need to implement the copy constructor to properly set the reference.

THE CHAIN OF RESPONSIBILITY PATTERN

A chain of responsibility is used when you want each class in an object-oriented hierarchy to get a crack at performing a particular action. The technique generally employs polymorphism so that the most specific class gets called first and can either handle the call or pass it up to its parent. The parent then makes the same decision — it can handle the call or pass it up to its parent. A chain of responsibility does not necessarily have to follow a class hierarchy, but it typically does.

Chains of responsibility are perhaps most commonly used for event handling. Many modern applications, particularly those with graphical user interfaces, are designed as a series of events and responses. For example, when a user clicks on the File menu and selects Open, an open event has occurred. When the user moves the mouse over the drawable area of a paint program, mouse move events are generated continuously. If the user presses down a button on the mouse, a mouse down event for that button-press is generated. The program will then start paying attention to the mouse move events, allowing the user to “draw” some object, and continue doing this until the mouse up event occurs. Each operating system has its own way of naming and using these events, but the overall idea is the same: When an event occurs, it is somehow communicated to the program, which takes appropriate action.

As you know, C++ does not have any built-in facilities for graphical programming. It also has no notion of events, event transmission, or event handling. A chain of responsibility is a reasonable approach to event handling because in an object-oriented hierarchy, the processing of events often maps to the class/subclass structure.

Example: Event Handling

Consider a drawing program, which has a hierarchy of Shape classes, as in Figure 29-8.

The leaf nodes handle certain events. For example, Square or Circle can receive mouse down events that will select the chosen shape. The parent class handles events that have the same effect regardless of the particular shape. For example, a delete event is handled the same way, regardless of the type of shape being deleted. The ideal algorithm for handling a particular event is to start at the leaf nodes and walk up the hierarchy until the message is handled. In other words, if a mouse down event occurs on a Square object, first the Square will get a chance to handle the event. If it doesn’t recognize the event, the Shape class gets a chance. This approach is an example of a chain of responsibility because each subclass may pass the message up to the next class in the chain.

Implementation of a Chain of Responsibility

The code for a chained messaging approach will vary based on how the operating system handles events, but it tends to resemble the following code, which uses integers to represent types of events:

void Square::handleMessage(int inMessage)
{
    switch (inMessage) {
        case kMessageMouseDown:
            handleMouseDown();
            break;
        case kMessageInvert:
            handleInvert();
            break;
        default:
            // Message not recognized--chain to superclass
            Shape::handleMessage(inMessage);
    }
}
void Shape::handleMessage(int inMessage)
{
    switch (inMessage) {
        case kMessageDelete:
            handleDelete();
            break;
        default:
        {
            stringstream ss;
            ss << __func__ << ": Unrecognized message received: " << inMessage;
            throw invalid_argument(ss.str());
        }
     }
}

When the event-handling portion of the program or framework receives a message, it finds the corresponding shape and calls handleMessage(). Through polymorphism, the subclass’s version of handleMessage() is called. This gives the leaf node first crack at handling the message. If it doesn’t know how to handle it, it passes it up to its superclass, which gets the next chance. In this example, the final recipient of the message throws an exception if it is unable to handle the event. You could also have your handleMessage() method return a boolean indicating success or failure.

Note that while event chains usually correlate with the class hierarchy, they do not have to. In the preceding example, the Square class could have just as easily passed the message to an entirely different object. The chained approach is flexible and has a very appealing structure for object-oriented hierarchies. The downside is that it requires diligence on the part of the programmer. If you forget to chain up to the superclass from a subclass, events will effectively get lost. Worse, if you chain to the wrong class, you could end up in an infinite loop!

Using a Chain of Responsibility

For a chain of responsibility to respond to events, there must be another class that dispatches the events to the correct object. Because this task varies greatly by framework or platform, the following example shows pseudocode for handling a mouse down event, in lieu of platform-specific C++ code:

MouseLocation loc = getClickLocation();
Shape* clickedShape = findShapeAtLocation(loc);
if (clickedShape)
    clickedShape->handleMessage(kMessageMouseDown); 

THE OBSERVER PATTERN

The other common model for event handling is known as observer, listener messaging, or publish and subscribe. This is a more prescriptive model that is often less error-prone than message chains. With the publish and subscribe technique, individual objects register the events they are able to understand with a central event handling registry. When an event is received, it is transmitted to the list of subscribed objects.

Example: Event Handling

Just as with the earlier chain of responsibility pattern, observers are often used to handle events. The main difference between the two patterns is that the chain of responsibility works best for logical hierarchies where you need to find the correct class to handle the event. Observers work best when events can be handled by multiple objects, or are unrelated to a hierarchy.

Implementation of an Observer

First, a Listener mix-in class is defined. Any object that wants to subscribe to one or more events should inherit from this mix-in class:

image
class Listener
{
    public:
        virtual void handleMessage(int inMessage) = 0;
};

Code snippet from ObserverListener.h

The definition of a simple event registry class is shown next. It allows any object that extends the mix-in class Listener to subscribe to one or more events. It maintains a directory of Listeners and their corresponding events. It also contains a method for the program to call when an event is received, which will dispense the event to all subscribed Listeners.

To simplify this example, a few assumptions are made. These assumptions are not necessarily going to be valid in all contexts. To focus the example on demonstrating the observer pattern, the event registry just uses static methods and a static map, meaning there is only one map during the run time of the program. If you need multiple separate event registries, you’ll have to adapt the EventRegistry class. Support for unregistering listeners is also not included. Normally, the destructor of a listener may want to unregister all subscriptions for that object. The given implementation also allows an object to register itself twice, which may be undesirable depending on your use case. You might also need to register objects with an implied ordering (for example, Z-axis for graphical objects) in which events are processed; in the following example, there is no implied ordering, so an std::map is used, which imposes its own order on event handling. Note that the model has many variants depending on the needs of the design. Thus, you should read this code for the overall structure, and not think of it as an exact model that is universally applicable in all circumstances.

image
#include "Listener.h"
#include <vector>
#include <map>
class EventRegistry
{
    public:
        static void registerListener(int inMessage, Listener* inListener);
        static void handleMessage(int inMessage);
    protected:
        static std::map<int, std::vector<Listener*>> sListenerMap;
};

Code snippet from ObserverEventRegistry.h

The implementation of the EventRegistry class follows. When a new Listener is registered, it is added to the vector of Listeners stored in the listener map for the given event. When an event is received, the registry simply retrieves the vector and passes the event to each Listener:

image
// Define the static map.
map<int, vector<Listener*>> EventRegistry::sListenerMap;
 
void EventRegistry::registerListener(int inMessage, Listener* inListener)
{
    // Chapter 12 explains std::map. Note that if you specify a key
    // using [] to access it, and there is not yet an entry in the map for
    // that key, a new entry is created.
    sListenerMap[inMessage].push_back(inListener);
}
void EventRegistry::handleMessage(int inMessage)
{ 
    // Check to see if the message has *any* listeners. This check is required
    // because otherwise, accessing sListenerMap[inMessage] would create
    // a new entry when it's not yet in the map. See Chapter 12.
    if (sListenerMap.find(inMessage) == sListenerMap.end())
        return;
    for (auto iter = sListenerMap[inMessage].begin();
              iter != sListenerMap[inMessage].end(); ++iter) {
        (*iter)->handleMessage(inMessage);
    }
}

Code snippet from ObserverEventRegistry.cpp

Using an Observer

Following is a very simple unit test that demonstrates how to use the publish and subscribe technique. The class TestListener subscribes to message 0 in its constructor. Subscribing to a message in a constructor is a common pattern for objects that are Listeners. The class contains two flags that keep track of whether message 0 was successfully received, and whether any unknown messages were received. If message 0 was received and no unknowns were received, the test passes.

image
class TestListener : public Listener
{
    public:
        TestListener();
        void handleMessage(int inMessage);
        bool bMessage0Received;
        bool bUnknownMessageReceived;
};

Code snippet from ObserverTestListener.h

image
TestListener::TestListener() :
    bMessage0Received(false),
    bUnknownMessageReceived(false)
{
    // Subscribe to event 0.
    EventRegistry::registerListener(0, this);
}
void TestListener::handleMessage(int inMessage) 
{
    switch (inMessage) {
        case 0:
            bMessage0Received = true;
            break;
        default:
            bUnknownMessageReceived = true;
            break;
    }
}

Code snippet from ObserverTestListener.cpp

image
int main()
{
    TestListener tl;
    EventRegistry::handleMessage(0);
    EventRegistry::handleMessage(1);
    EventRegistry::handleMessage(2);
    if (!tl.bMessage0Received) {
        cout << "TEST FAILED: Message 0 was not received" << endl;
    } else if (tl.bUnknownMessageReceived) {
        cout << "TEST FAILED: TestListener received unknown message" << endl;
    } else {
        cout << "TEST PASSED" << endl;
    }
    return 0;
}

Code snippet from ObserverTestObserver.cpp

Of course, an actual implementation in your program would vary from the implementation shown here based on the services provided by the environment and your individual needs.

SUMMARY

This chapter has given you just a taste of how patterns can help you organize object-oriented concepts into high-level designs. There is a seemingly infinite supply of design patterns cataloged and discussed on the Portland Pattern Repository Wiki at www.c2.com. It’s easy to get carried away and spend all your time trying to find the specific pattern that applies to your task. We recommend that you focus on a few patterns that interest you and focus your learning on how patterns are developed, not just the small differences between similar ones. After all, to paraphrase the old saying, “Teach me a design pattern, and I’ll code for a day. Teach me how to create design patterns, and I’ll code for a lifetime.”

Design patterns are a terrific way to end your journey through Professional C++ Programming because they are a perfect example of how good C++ programmers can become great C++ programmers. By thinking through your designs, experimenting with different approaches in object-oriented programming, and selectively adding new techniques to your coding repertoire, you’ll be able to take your C++ skills to the Professional level.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset