Provide an interface for creating families of related or dependent objects without specifying their concrete classes.
Kit
Consider a user interface toolkit that supports multiple look-and-feel standards, such as Motif and Presentation Manager. Different look-and-feels define different appearances and behaviors for user interface "widgets" like scroll bars, windows, and buttons. To be portable across look-and-feel standards, an application should not hard-code its widgets for a particular look and feel. Instantiating look-and-feel-specific classes of widgets throughout the application makes it hard to change the look and feel later.
We can solve this problem by defining an abstract WidgetFactory class that declares an interface for creating each basic kind of widget. There's also an abstract class for each kind of widget, and concrete subclasses implement widgets for specific look-and-feel standards. WidgetFactory's interface has an operation that returns a new widget object for each abstract widget class. Clients call these operations to obtain widget instances, but clients aren't aware of the concrete classes they're using. Thus clients stay independent of the prevailing look and feel.
There is a concrete subclass of WidgetFactory for each look-and-feel standard. Each subclass implements the operations to create the appropriate widget for the look and feel. For example, the CreateScrollBar operation on the MotifWidgetFactory instantiates and returns a Motif scroll bar, while the corresponding operation on the PMWidgetFactory returns a scroll bar for Presentation Manager. Clients create widgets solely through the WidgetFactory interface and have no knowledge of the classes that implement widgets for a particular look and feel. In other words, clients only have to commit to an interface defined by an abstract class, not a particular concrete class.
A WidgetFactory also enforces dependencies between the concrete widget classes. A Motif scroll bar should be used with a Motif button and a Motif text editor, and that constraint is enforced automatically as a consequence of using a MotifWidgetFactory.
Use the Abstract Factory pattern when
The Abstract Factory pattern has the following benefits and liabilities:
Here are some useful techniques for implementing the Abstract Factory pattern.
If many product families are possible, the concrete factory can be implemented using the Prototype (117) pattern. The concrete factory is initialized with a prototypical instance of each product in the family, and it creates a new product by cloning its prototype. The Prototype-based approach eliminates the need for a new concrete factory class for each new product family.
Here's a way to implement a Prototype-based factory in Smalltalk. The
concrete factory stores the prototypes to be cloned in a dictionary
called partCatalog
. The method make:
retrieves the
prototype and clones it:
make: partName ^ (partCatalog at: partName) copy
The concrete factory has a method for adding parts to the catalog.
addPart: partTemplate named: partName partCatalog at: partName put: partTemplate
Prototypes are added to the factory by identifying them with a symbol:
aFactory addPart: aPrototype named: #ACMEWidget
A variation on the Prototype-based approach is possible in languages that treat classes as first-class objects (Smalltalk and Objective C, for example). You can think of a class in these languages as a degenerate factory that creates only one kind of product. You can store classes inside a concrete factory that create the various concrete products in variables, much like prototypes. These classes create new instances on behalf of the concrete factory. You define a new factory by initializing an instance of a concrete factory with classes of products rather than by subclassing. This approach takes advantage of language characteristics, whereas the pure Prototype-based approach is language-independent.
Like the Prototype-based factory in Smalltalk just discussed, the
class-based version will have a single instance variable
partCatalog
, which is a dictionary whose key is the name of
the part. Instead of storing prototypes to be cloned,
partCatalog
stores the classes of the products. The method
make:
now looks like this:
make: partName ^ (partCatalog at: partName) new
A more flexible but less safe design is to add a parameter to operations that create objects. This parameter specifies the kind of object to be created. It could be a class identifier, an integer, a string, or anything else that identifies the kind of product. In fact with this approach, AbstractFactory only needs a single "Make" operation with a parameter indicating the kind of object to create. This is the technique used in the Prototype- and the class-based abstract factories discussed earlier.
This variation is easier to use in a dynamically typed language like Smalltalk than in a statically typed language like C++. You can use it in C++ only when all objects have the same abstract base class or when the product objects can be safely coerced to the correct type by the client that requested them. The implementation section of Factory Method (107) shows how to implement such parameterized operations in C++.
But even when no coercion is needed, an inherent problem remains: All
products are returned to the client with the same abstract
interface as given by the return type. The client will not be able to
differentiate or make safe assumptions about the class of a product.
If clients need to perform subclass-specific operations, they won't be
accessible through the abstract interface. Although the client could
perform a downcast (e.g., with dynamic_cast
in C++), that's
not always feasible or safe, because the downcast can fail. This is the
classic trade-off for a highly flexible and extensible interface.
We'll apply the Abstract Factory pattern to creating the mazes we discussed at the beginning of this chapter.
Class MazeFactory
can create components of mazes. It builds
rooms, walls, and doors between rooms. It might be used by a program
that reads plans for mazes from a file and builds the corresponding
maze. Or it might be used by a program that builds mazes randomly.
Programs that build mazes take a MazeFactory
as an argument
so that the programmer can specify the classes of rooms, walls, and
doors to construct.
class MazeFactory { public: MazeFactory(); virtual Maze* MakeMaze() const { return new Maze; } virtual Wall* MakeWall() const { return new Wall; } virtual Room* MakeRoom(int n) const { return new Room(n); } virtual Door* MakeDoor(Room* r1, Room* r2) const { return new Door(r1, r2); } };
Recall that the member function
CreateMaze
(page 84)
builds a small maze consisting of two rooms with a door between them.
CreateMaze
hard-codes the class names, making it difficult
to create mazes with different components.
Here's a version of CreateMaze
that remedies that
shortcoming by taking a MazeFactory
as a parameter:
Maze* MazeGame::CreateMaze (MazeFactory& factory) { Maze* aMaze = factory.MakeMaze(); Room* r1 = factory.MakeRoom(1); Room* r2 = factory.MakeRoom(2); Door* aDoor = factory.MakeDoor(r1, r2); aMaze->AddRoom(r1); aMaze->AddRoom(r2); r1->SetSide(North, factory.MakeWall()); r1->SetSide(East, aDoor); r1->SetSide(South, factory.MakeWall()); r1->SetSide(West, factory.MakeWall()); r2->SetSide(North, factory.MakeWall()); r2->SetSide(East, factory.MakeWall()); r2->SetSide(South, factory.MakeWall()); r2->SetSide(West, aDoor); return aMaze; }
We can create EnchantedMazeFactory
, a factory for enchanted
mazes, by subclassing MazeFactory
.
EnchantedMazeFactory
will override different member
functions and return different subclasses of
Room
, Wall
, etc.
class EnchantedMazeFactory : public MazeFactory { public: EnchantedMazeFactory(); virtual Room* MakeRoom(int n) const { return new EnchantedRoom(n, CastSpell()); } virtual Door* MakeDoor(Room* r1, Room* r2) const { return new DoorNeedingSpell(r1, r2); } protected: Spell* CastSpell() const; };
Now suppose we want to make a maze game in which a room can have a
bomb set in it. If the bomb goes off, it will damage the walls (at
least). We can make a subclass of Room
keep track of
whether the room has a bomb in it and whether the bomb has gone off.
We'll also need a subclass of Wall
to keep track of the
damage done to the wall. We'll call these classes
RoomWithABomb
and BombedWall
.
The last class we'll define is BombedMazeFactory
, a subclass
of MazeFactory
that ensures walls are of class
BombedWall
and rooms are of class RoomWithABomb
.
BombedMazeFactory
only needs to override two functions:
Wall* BombedMazeFactory::MakeWall () const { return new BombedWall; } Room* BombedMazeFactory::MakeRoom(int n) const { return new RoomWithABomb(n); }
To build a simple maze that can contain bombs, we simply call
CreateMaze
with a BombedMazeFactory
.
MazeGame game; BombedMazeFactory factory; game.CreateMaze(factory);
CreateMaze
can take an instance of
EnchantedMazeFactory
just as well to build enchanted
mazes.
Notice that the MazeFactory
is just a collection of factory
methods. This is the most common way to implement the Abstract
Factory pattern. Also note that MazeFactory
is not an
abstract class; thus it acts as both the AbstractFactory and the
ConcreteFactory. This is another common implementation for simple
applications of the Abstract Factory pattern. Because the MazeFactory
is a concrete class consisting
entirely of factory methods, it's easy to make a new
MazeFactory
by making a subclass and overriding the
operations that need to change.
CreateMaze
used the SetSide
operation on rooms to
specify their sides. If it creates rooms with a
BombedMazeFactory
, then the maze will be made up of
RoomWithABomb
objects with BombedWall
sides. If
RoomWithABomb
had to access a subclass-specific member of
BombedWall
, then it would have to cast a
reference to its walls from Wall*
to
BombedWall*
. This downcasting is safe as long as the argument
is in fact a BombedWall
, which is guaranteed to be
true if walls are built solely with a BombedMazeFactory
.
Dynamically typed languages such as Smalltalk don't require downcasting,
of course, but they might produce run-time errors if they encounter a
Wall
where they expect a subclass of Wall
.
Using Abstract Factory to build walls helps prevent these run-time
errors by ensuring that only certain kinds of walls can be
created.
Let's consider a Smalltalk version of MazeFactory
, one with
a single make
operation that takes the kind of object to
make as a parameter. Moreover, the concrete factory stores the
classes of the products it creates.
First, we'll write an equivalent of CreateMaze
in
Smalltalk:
createMaze: aFactory | room1 room2 aDoor | room1 := (aFactory make: #room) number: 1. room2 := (aFactory make: #room) number: 2. aDoor := (aFactory make: #door) from: room1 to: room2. room1 atSide: #north put: (aFactory make: #wall). room1 atSide: #east put: aDoor. room1 atSide: #south put: (aFactory make: #wall). room1 atSide: #west put: (aFactory make: #wall). room2 atSide: #north put: (aFactory make: #wall). room2 atSide: #east put: (aFactory make: #wall). room2 atSide: #south put: (aFactory make: #wall). room2 atSide: #west put: aDoor. ^ Maze new addRoom: room1; addRoom: room2; yourself
As we discussed in the Implementation section, MazeFactory
needs only a single instance variable partCatalog
to provide
a dictionary whose key is the class of the component. Also recall how
we implemented the make:
method:
make: partName ^ (partCatalog at: partName) new
Now we can create a MazeFactory
and use it to implement
createMaze
. We'll create the factory using a method
createMazeFactory
of class MazeGame
.
createMazeFactory ^ (MazeFactory new addPart: Wall named: #wall; addPart: Room named: #room; addPart: Door named: #door; yourself)
A BombedMazeFactory
or EnchantedMazeFactory
is
created by associating different classes with the keys. For example,
an EnchantedMazeFactory
could be created like this:
createMazeFactory ^ (MazeFactory new addPart: Wall named: #wall; addPart: EnchantedRoom named: #room; addPart: DoorNeedingSpell named: #door; yourself)
InterViews uses the "Kit" suffix [Lin92] to denote AbstractFactory classes. It defines WidgetKit and DialogKit abstract factories for generating look-and-feel-specific user interface objects. InterViews also includes a LayoutKit that generates different composition objects depending on the layout desired. For example, a layout that is conceptually horizontal may require different composition objects depending on the document's orientation (portrait or landscape).
ET++ [WGM88] uses the Abstract Factory pattern to achieve portability across different window systems (X Windows and SunView, for example). The WindowSystem abstract base class defines the interface for creating objects that represent window system resources (MakeWindow, MakeFont, MakeColor, for example). Concrete subclasses implement the interfaces for a specific window system. At run-time, ET++ creates an instance of a concrete WindowSystem subclass that creates concrete system resource objects.
AbstractFactory classes are often implemented with factory methods (Factory Method (107)), but they can also be implemented using Prototype (117).
A concrete factory is often a singleton (Singleton (127)).