It’s interesting how intricate implementing such a simple pattern as Singleton can be. We haven’t even addressed issues of thread safety, and yet many pages have elapsed since the beginning of this section. The last thing we wish to say about Singleton is that it should be used sparingly. True Singleton objects arise rarely, and the last thing a Singleton should be used for is to replace a global variable.[120]
Classifying patterns
Design Patterns discusses 23 patterns, classified under three purposes (all of which revolve around the particular aspect that can vary):
1.Creational: how an object can be created. This often involves isolating the details of object creation so your code isn’t dependent on what types of objects there are and thus doesn’t have to be changed when you add a new type of object. The aforementioned Singleton is classified as a creational pattern, and later in this chapter you’ll see examples of Factory Method.
2.Structural: designing objects to satisfy particular project constraints. These affect the way objects are connected with other objects to ensure that changes in the system don’t require changes to those connections.
3.Behavioral: objects that handle particular types of actions within a program. These encapsulate processes that you want to perform, such as interpreting a language, fulfilling a request, moving through a sequence (as in an iterator), or implementing an algorithm. This chapter contains examples of the Observer and the Visitor patterns.
Design Patterns includes a section on each of its 23 patterns along with one or more examples for each, typically in C++ but sometimes in Smalltalk. This book will not repeat all the details of the patterns shown in Design Patterns since that book stands on its own and should be studied separately. The catalog and examples provided here are intended to rapidly give you a grasp of the patterns, so you can get a decent feel for what patterns are about and why they are so important.
Features, idioms, patterns
Work is continuing beyond what is in the GoF book, of course; hence, there are more patterns and a more refined process on defining design patterns in general.[121] This is important because it is not easy to identify new patterns or to properly describe them. There is some confusion in the popular literature on what a design pattern is, for example. Patterns are not trivial nor are they typically represented by features that are built into a programming language. Constructors and destructors, for example, could be called the "guaranteed initialization and cleanup design pattern." These are important and essential constructs, but they’re routine language constructs and are not rich enough to be considered a design pattern.
Another non-example comes from various forms of aggregation. Aggregation is a completely fundamental principle in object-oriented programming: you make objects out of other objects. Yet sometimes this idea is erroneously classified as a pattern. This is unfortunate because it pollutes the idea of the design pattern and suggests that anything that surprises you the first time you see it should be made into a design pattern.
Yet another misguided example is found in the Java language; the designers of the JavaBeans specification decided to refer to the simple "get/set" naming convention as a design pattern (for example, getInfo( ) returns an Info property and setInfo( ) changes it). This is just a commonplace naming convention and in no way constitutes a design pattern.
Building complex objects
The class that will be created in the next example models a bicycle that can have a choice of parts, according to its type (mountain bike, touring bike, or racing bike). This is called the Builder design pattern. A builder class is associated with each flavor of bicycle, each of which implements the interface specified in the abstract class BicycleBuilder. A separate class, BicycleTechnician, uses a concrete BicycleBuilder object to construct a Bicycle object.
//: C10:Bicycle.h
// Defines classes to build bicycles
// Illustrates the Builder Design Pattern
#ifndef BICYCLE_H
#define BICYCLE_H
#include <iosfwd>
#include <string>
#include <vector>
class BicyclePart {
public:
enum BPart {FRAME, WHEEL, SEAT, DERAILLEUR,
HANDLEBAR, SPROCKET, RACK, SHOCK, NPARTS};
BicyclePart(BPart);
friend std::ostream&
operator<<(std::ostream&, const BicyclePart&);
private:
BPart id;
static std::string names[NPARTS];
};
class Bicycle {
public:
~Bicycle();
void addPart(BicyclePart*);
friend std::ostream&
operator<<(std::ostream&, const Bicycle&);
private:
std::vector<BicyclePart*> parts;
};
class BicycleBuilder {
public:
BicycleBuilder() {
product = 0;
}
void createProduct() {
product = new Bicycle;
}
virtual void buildFrame() = 0;
virtual void buildWheel() = 0;
virtual void buildSeat() = 0;
virtual void buildDerailleur() = 0;
virtual void buildHandlebar() = 0;
virtual void buildSprocket() = 0;
virtual void buildRack() = 0;
virtual void buildShock() = 0;
virtual std::string getBikeName() const = 0;
Bicycle* getProduct() {
Bicycle* temp = product;
product = 0; // relinquish product
return temp;
}
protected:
Bicycle* product;
};
class MountainBikeBuilder : public BicycleBuilder {
public:
void buildFrame();
void buildWheel();
void buildSeat();
void buildDerailleur();
void buildHandlebar();
void buildSprocket();
void buildRack();
void buildShock();
std::string getBikeName() const {
return "MountainBike";
}
};
class TouringBikeBuilder : public BicycleBuilder {
public:
void buildFrame();
void buildWheel();
void buildSeat();
void buildDerailleur();
void buildHandlebar();
void buildSprocket();
void buildRack();
void buildShock();
std::string getBikeName() const {
return "TouringBike";
}
};
class RacingBikeBuilder : public BicycleBuilder {
public:
void buildFrame();
void buildWheel();
120
For more information, see the article “Once is Not Enough” by Hyslop and Sutter in the March 2003 issue of