24
Software Development Methods 1 CHAPTER 3 An Introduction to Design Patterns Objectives The objectives of this chapter are to identify the following: Describe the purposes of design patterns. Examine the Singleton design pattern. Examine the Abstract Factory design pattern. Create basic UML diagrams for these design patterns.

CHAPTER 3 An Introduction to Design Patterns · An Introduction to Design Patterns 2 Software Development Methods Design Patterns One of the most frequently touted advantages of object-oriented

  • Upload
    others

  • View
    12

  • Download
    0

Embed Size (px)

Citation preview

Page 1: CHAPTER 3 An Introduction to Design Patterns · An Introduction to Design Patterns 2 Software Development Methods Design Patterns One of the most frequently touted advantages of object-oriented

Software Development Methods 1

CHAPTER 3 An Introduction to Design Patterns

Objectives

The objectives of this chapter are to identify the following:

• Describe the purposes of design patterns.• Examine the Singleton design pattern.• Examine the Abstract Factory design pattern.• Create basic UML diagrams for these design patterns.

Page 2: CHAPTER 3 An Introduction to Design Patterns · An Introduction to Design Patterns 2 Software Development Methods Design Patterns One of the most frequently touted advantages of object-oriented

An Introduction to Design Patterns

2 Software Development Methods

Design Patterns One of the most frequently touted advantages of object-oriented design is reuse. Unfortunately, it is difficult for inexperienced developers and designers to spot reusable components and even more difficult for them to implement their design so that reuse is maximized.

It is also common for different software engineers have their own techniques for developing software. One engineer’s favorite techniques may not be used by oth-ers in the field. Once that engineer leaves, it becomes difficult to maintain and extend their design. The end result may be increased re-work which negates the advantages of reusable code.

Design patterns are a way of designing software that maximizes reuse while pro-viding a common structure that can easily be understood by other software engi-neering practitioners.

According to Christopher Alexander there are many similarities between soft-ware design and architecture:

• Both are creative processes with a large number of equally valid possibilities.• Both must eventually satisfy the customer’s needs.• Both must satisfy various engineering constraints.• Both seek some intrinsic, unquantifieble qualities.

Christopher Alexander suggested that buildings could be constructed using com-binations of a relatively small number of patterns:

“Each pattern describes a problem which occurs over and over again in our envi-ronment, and then describes the core of the solution to that problem in such a way that you can use this solution a million times over, without ever doing it the same way twice.”1

These design patterns have specific characteristics:

• They are not primitive building blocks. These patterns are ways of organizing primitive building blocks to allow the development of more complex designs, but the designs are not themselves those primitives.

• They are not complex, domain specific designs. These designs are meant to be used over and over across multiple domains.

1. “A Pattern Language: Towns, Buildings, Construction”, Christopher Alexander, Sara Ishikawa, Murray Silverstein, Oxford University Press, 1977.

Page 3: CHAPTER 3 An Introduction to Design Patterns · An Introduction to Design Patterns 2 Software Development Methods Design Patterns One of the most frequently touted advantages of object-oriented

Software Development Methods 3

An Introduction to Design Patterns

A Pattern Language Alexander describes a pattern language for the construction of buildings. In this description he uses two key components:

• Elements. These are the patterns themselves.• Sentences. Expressions of design using the elements.

Alexander describes the process of using these patterns to construct buildings as:

“It is possible to make buildings by stringing together patterns, in a rather loose way. A building made like this, is an assembly of patterns. It is not dense. It is not profound. But it is also possible to put patterns together in a way that many pat-terns overlap in the same physical space: the building is very dense; it has many meanings captured in a small space; and through this density, it becomes pro-found.”

This essentially means that the while the patterns themselves are not compli-cated, they may be combined in such a way as to create complicated structures. The difference between using patterns to construct such buildings and not using those patterns is one of organization and understanding. It will be easier to under-stand the design if it is known that the design is made up of well understood and easily recognizable patterns.

Software Design Patterns

In this course we are attempting to adapt some of Alexander’s design patterns to the study of software design. Specifically we are seeking to:

• Capture software design expertise using a relatively small number of patterns.• Intend to be used over and over without ever needing to do it the same way

twice.• Aim to enhance reusability, extensibility, and maintainability.

Software design patterns may be used across the software spectrum:

• Application programs with a specific domain and purpose.• Toolkits that are a collection of general purpose classes.• Frameworks which are classes with a specific architecture and style. Such

frameworks are generally domain specific and yet highly customizable.

Design Pattern Anatomy

Each design pattern has a specific style of documentation:

• Name. The name of the pattern. This should be something that provides some indication of what the pattern does.

Page 4: CHAPTER 3 An Introduction to Design Patterns · An Introduction to Design Patterns 2 Software Development Methods Design Patterns One of the most frequently touted advantages of object-oriented

An Introduction to Design Patterns

4 Software Development Methods

• Problem or intent. The kind of problem that the pattern is designed to solve. This is usually accompanied by some kind of specific example designed to show the problem.

• Solution. The pattern is implemented, frequently on the example show in the problem section, and the new design is analyzed to show its strengths.

Design Pattern Categories

Most design patterns fit into one or more categories. These categories or orga-nized to give an indication of when such a pattern will be appropriate. There are three main patterns within your book:

• Creational. These patterns have to do with how objects are created and initial-ized.

• Structural. These patterns control the composition and structure of an object.• Behavioral. These patterns provide the ability to alter the behavior of objects

dynamically.

Creational Patterns There are many creational patterns. Below are some of them:

• Abstract Factory. Create families of related objects.• Builder. Create composite objects of different representations.• Factory Method. Create different concrete objects.• Prototype. Create new object by cloning.• Singleton. Ensure a class has only one instance.

During this lecture we will cover the abstract factory and singleton patterns. They are closely related and can yet be examined individually.

Introduction to the Sample

Your book uses an interesting example as the basis for discussing creational design patterns, specifically, building a maze for a computer game. While this sample leaves out a great many details, we’ll consider that an exercise in abstrac-tion. For this example we focus strictly on the mechanisms used to create the maze itself.

In this example we consider a maze to be made up of three main components:

• Rooms• Doors• Walls

Page 5: CHAPTER 3 An Introduction to Design Patterns · An Introduction to Design Patterns 2 Software Development Methods Design Patterns One of the most frequently touted advantages of object-oriented

Software Development Methods 5

An Introduction to Design Patterns

It should be easy to see that this could be extended to include other items. Your book has a basic diagram of the relationships between the classes on page 82.

The code is written in C++. Since this is the language of choice for most of you, that is the language I will stick to. Where possible, I will try to provide some sample of Java code as well.

Directions Directions have been implemented as an enumerated type:

enum Direction { North, South, East, West };

Although the rules around enumerations are fairly involved, at a high level this means that a new type, Direction, exists and that variables of this type may only be set to the values identified in the enumerator list: North, South, East, and West.

In Java the simplest way to do this might be to declare an interface and embed the constant values inside:

public interface Direction {public static final int North = 0;public static final int South = 1;public static final int East = 2;public static final int West = 3;

}

This interface may then be implemented by whichever class needed access to the directional constants.

MapSite We now turn our attention to the MapSite class. This will be an abstract class which means that we cannot create an object of type MapSite. The code, as provided by your book is as follows:

class MapSite {public:

virtual void enter() = 0;};

The Java class will be similar:

public abstract class MapSite {public abstract void enter();

};

Page 6: CHAPTER 3 An Introduction to Design Patterns · An Introduction to Design Patterns 2 Software Development Methods Design Patterns One of the most frequently touted advantages of object-oriented

An Introduction to Design Patterns

6 Software Development Methods

The idea in either case is that the enter() method is a virtual function. This means that it is up to subclasses inherited from MapSite to implement the logic behind their own variations of the enter() method. This is called polymor-phism.

Room The Room class is a concrete derived class inherited from the abstract MapSite base class. Rooms maintain references to other MapSite instances and stores various information. In this sample each Room only has a room number, but you could extend the idea of room contents to include such things as monsters, trea-sure, high explosives, etc.

The C++ declarations for the Room class is given by:

class Room : public MapSite {private:

MapSite * _sides[4];int _roomNumber;

public:Room(int roomNo);MapSite * getSide(Direction) const;void setSide(Direction, MapSite *);virtual void enter();

};

The Java representation of the Room class might be defined as follows:

public class Room extends MapSite {private MapSite _sides[4];private int _roomNumber;public Room(int roomNo) { ... }public MapSite getSide(int d) { ... }public void setSide(int d, MapSite m) { ... }public void enter() { ... }

};

Wall The C++ representation of the Wall class appears as:

class Wall : public MapSite {public:

Wall();virtual void enter();

};

Page 7: CHAPTER 3 An Introduction to Design Patterns · An Introduction to Design Patterns 2 Software Development Methods Design Patterns One of the most frequently touted advantages of object-oriented

Software Development Methods 7

An Introduction to Design Patterns

The Java representation of the Wall class might be given by:

public class Wall extends MapSite {public Wall() { ... }public void enter() { ... }

};

Door The book provide the following C++ Door declaration:

class Door : public MapSite {private:

Room * _room1;Room * _room2;bool _isOpen;

public:Door (Room * = 0, Room * = 0);Room * otherSideFrom(Room *);virtual void enter();

}:

A corresponding Java Door might appear as:

public class Door extends MapSite {private Room _room1;private Room _room2;private boolean _isOpen;public Door (Room r1, Room r2) { ... }public Room otherSideFrom(Room r) { ... };public void enter() { ... };

}:

Maze The remaining piece of this puzzle is to define a Maze class that builds a maze out of these other constituent parts. For this example, Mazes need to know about a collection of Rooms and can locate a specific Room given its room number attribute.

class Maze {private:

Room * _rooms;public:

Maze();void addRoom(Room *);Room * roomNo(int) const;

}:

Page 8: CHAPTER 3 An Introduction to Design Patterns · An Introduction to Design Patterns 2 Software Development Methods Design Patterns One of the most frequently touted advantages of object-oriented

An Introduction to Design Patterns

8 Software Development Methods

Note that the private data represents a collection of Rooms and possibly any other relevant data. In this example the collection of rooms is represented as some array of rooms that will be allocated at run-time.

One possible Java representation of this object might be:

public class Maze {private Vector _rooms;public Maze() { ... }public void addRoom(Room r) { ... }public Room roomNo(int n) { ... }

}:

In this case the collection of rooms is stored in a Java Vector which is a dynamic collection that can grow to accommodate additional rooms as they are added to the maze.

MazeGame The final class we need will build the actual Maze. This class will be called MazeGame. For this example we will assume the MazeGame class contains a static method that is used to create a new Maze object. Thus the C++ definition of the class might appear as:

class MazeGame {public:

static Maze * CreateMaze(void);}:

The corresponding Java class might be represented as:

public class MazeGame {public static Maze CreateMaze() { ... }

}:

Constructing the Maze Your book makes mention of a simplistic approach to constructing a Maze, spe-cifically, by adding components to the Maze and then interconnecting them. This would take place in the CreateMaze method of the MazeGame class. The book’s C++ code constructs a simple Maze of two Rooms with a Door between them:

Page 9: CHAPTER 3 An Introduction to Design Patterns · An Introduction to Design Patterns 2 Software Development Methods Design Patterns One of the most frequently touted advantages of object-oriented

Software Development Methods 9

An Introduction to Design Patterns

Maze * MazeGame::CreateMaze(void) {Maze * aMaze = new Maze();Room * r1 = new Room(1);Room * r2 = new Room(2);Door * theDoor = new Door(r1, r2);

aMaze->addRoom(r1);aMaze->addRoom(r2);

r1->setSide(North, new Wall());r1->setSide(East, theDoor);r1->setSide(South, new Wall());r1->setSide(West, new Wall());

r2->setSide(North, new Wall());r2->setSide(East, new Wall());r2->setSide(South, new Wall());r2->setSide(West, theDoor);

return (aMaze);}

The Java code is given by:

public Maze CreateMaze() {Maze aMaze = new Maze();Room r1 = new Room(1);Room r2 = new Room(2);Door theDoor = new Door(r1, r2);

aMaze.addRoom(r1);aMaze.addRoom(r2);

r1.setSide(Direction.North, new Wall());r1.setSide(Direction.East, theDoor);r1.setSide(Direction.South, new Wall());r1.setSide(Direction.West, new Wall());

r2.setSide(Direction.North, new Wall());r2.setSide(Direction.East, new Wall());r2.setSide(Direction.South, new Wall());r2.setSide(Direction.West, theDoor);

return (aMaze);}

Page 10: CHAPTER 3 An Introduction to Design Patterns · An Introduction to Design Patterns 2 Software Development Methods Design Patterns One of the most frequently touted advantages of object-oriented

An Introduction to Design Patterns

10 Software Development Methods

So what’s the problem?

Everything seems to be alright in this case. After all, we’re getting the Maze we asked for: two Rooms with a Door in between them.

Technically this is correct, but consider how inflexible this design is. The Maze’s layout is embedded within the CreateMaze method. Suppose I want to be able to design different Mazes. Then I would need to do one of two things:

• Override the CreateMaze method.• Changing parts of it.

Either of these approaches is error prone and may lead to code that is difficult to maintain and impossible to reuse. Add to this the fact that it is difficult to add new features such as:

• DoorNeedingSpell• EnchantedRoom

and you begin to get a glimmer of what the creational design patterns are used for. Essentially the creation patterns provide ways of removing explicit refer-ences to concrete classes from the code that instantiates them. In the case of the first design pattern, Abstract Factory, consider the following scenario:

• If CreateMaze is passed an object as a parameter to use to create the vari-ous MapSite objects, then you can change the classes of Rooms, Walls, and Doors by passing different parameters.

Page 11: CHAPTER 3 An Introduction to Design Patterns · An Introduction to Design Patterns 2 Software Development Methods Design Patterns One of the most frequently touted advantages of object-oriented

Software Development Methods 11

An Introduction to Design Patterns

Abstract Factories The Abstract Factory pattern should be used to provide an interface for creating families of related objects. More generally, use this pattern when:

• A system should be independent of how its products are created, composed, and represented.

• A system should be configured with one of many families of products.• A family of related product objects is designed to be used together and you

need to enforce this constraint.• You want to provide a class library of products, and you want to reveal only

their interfaces, not their implementations.

Abstract Factories have only two real participants:

• An abstract class which acts as the factory• A set of concrete subclasses inherited from the abstract class.

Finally, the results we gain from implementing an abstract factory are:

• Isolation of the concrete classes. The factory is responsible for creating the concrete objects such as Rooms, Doors, and Walls. Therefore the rest of the application does not need to know about how that process takes place.

• Ease of exchanging product families. A concrete factory is only created once in any given application. This can be enforced using the Singleton design pat-tern which we discuss later. Thus by changing which type of concrete factory is instantiated, we can change the kind of concrete objects are constructed.

• Consistency among products. If there is a constraint around the inter-operabil-ity of objects within or across families, the use of a factory guarantees that all of the correct kinds of objects are constructed.

• Somewhat difficult to extend. In order to add new “products” to the factory, you need to change the abstract factory’s interface and then add the concrete implementations of those new methods in each of the concrete factories. This may be error prone and time consuming. Your book discusses techniques that can be used to make such changes less onerous, but they each have trade-offs.

Maze Factory Our abstract factory pattern will center around a new class called MazeFac-tory. A MazeFactory defines an interface that is used to identify the kinds of “products” that the factory produces.

Your book provides the following C++ definition for a MazeFactory class:

Page 12: CHAPTER 3 An Introduction to Design Patterns · An Introduction to Design Patterns 2 Software Development Methods Design Patterns One of the most frequently touted advantages of object-oriented

An Introduction to Design Patterns

12 Software Development Methods

class MazeFactory {public:

MazeFactory();

virtual Maze * makeMaze(void) const{ return new Maze(); }

virtual Wall * makeWall(void) 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); }

};

The Java equivalent of the MazeFactory class is given by:

public class MazeFactory {public MazeFactory() { ... }

public Maze makeMaze(){ return new Maze(); }

public Wall makeWall(){ return new Wall(); }

public Room makeRoom(int n){ return new Room(n); }

public Door makeDoor(Room r1, Room r2){ return new Door(r1, r2); }

};

Using the Factory Let’s revisit the CreateMaze method of the MazeGame class. Currently that method hard-codes the class types that it is allocating. By providing the factory of choice to the method as an argument we can now, for all intents and purposes, parameterize the data types being used within our code.

Our new C++ implementation of CreateMaze appears as follows:

Page 13: CHAPTER 3 An Introduction to Design Patterns · An Introduction to Design Patterns 2 Software Development Methods Design Patterns One of the most frequently touted advantages of object-oriented

Software Development Methods 13

An Introduction to Design Patterns

Maze * MazeGame::CreateMaze(MazeFactory & factory) {Maze * aMaze = factory.makeMaze();Room * r1 = factory.makeRoom(1);Room * r2 = factory.Room(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.Wall());r2->setSide(West, aDoor);

return (aMaze);}

The Java implementation might appear as follows:

public Maze 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(Direction.North, factory.makeWall());r1.setSide(Direction.East, aDoor);r1.setSide(Direction.South, factory.makeWall());r1.setSide(Direction.West, factory.makeWall());

r2.setSide(Direction.North, factory.makeWall());r2.setSide(Direction.East, factory.makeWall());r2.setSide(Direction.South, factory.makeWall());r2.setSide(Direction.West, aDoor);

return (aMaze);}

Page 14: CHAPTER 3 An Introduction to Design Patterns · An Introduction to Design Patterns 2 Software Development Methods Design Patterns One of the most frequently touted advantages of object-oriented

An Introduction to Design Patterns

14 Software Development Methods

The great part about this pattern is that the kind of MazeFactory passed to the CreateMaze method will govern the kind of Maze that is actually generated. The default behavior for our scenario creates a “plain” Maze.

Enchanted Maze Factory

Suppose we wanted to be able to create enchanted mazes for fantasy games. This can be accomplished simply by subclassing a new concrete factory from the abstract MazeFactory. Thus we might end up with a new EnchantedMaze-Factory factory class that is responsible for creating enchanted maze objects:

class EnchantedMazeFactory : public MazeFactory {protected:

Spell * castSpell(void) const;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); }

};

The Java version of the EnchantedMazeFactory class is given by:

public class EnchantedMazeFactory {protected Spell castSpell() { ... }

public EnchantedMazeFactory() { ... }

public Room makeRoom(int n){ return new EnchantedRoom(n, castSpell()); }

public Door makeDoor(Room r1, Room r2){ return new DoorNeedingSpell(r1, r2); }

};

Bombed Maze Factory Now we move on to the classic case of a room that contains a bomb. The idea is that if the bomb goes off it will damage both the Room and the player. To support exploded and un-exploded bombs in rooms, we’ll need two new classes: BombedWall and RoomWithABomb.

Finally, we’ll also need a new concrete factory, BombedMazeFactory, which is able to produce these classes:

Page 15: CHAPTER 3 An Introduction to Design Patterns · An Introduction to Design Patterns 2 Software Development Methods Design Patterns One of the most frequently touted advantages of object-oriented

Software Development Methods 15

An Introduction to Design Patterns

class BombedMazeFactory : public MazeFactory {public:

BombedMazeFactory();

virtual Room * makeRoom(int n) const{ return new RoomWithABomb(n); }

virtual Wall * makeWall(void) const{ return new BombedWall(); }

};

The Java version of the BombedMazeFactory class is given by:

public class EnchantedMazeFactory {public BombedMazeFactory() { ... }

public Room makeRoom(int n){ return new RoomWithABomb(n); }

public Wall makeWall(){ return new BombedWall(); }

};

Creating Mazes Now we can see how simple it is to construct different kinds of Mazes simply by altering the factory that is used to construct them. For example, the following code generates a “plain” Maze:

MazeGame game;MazeFactory factory;Maze * maze;maze = game.CreateMaze(factory);

The Java code to do the same task is given by:

MazeGame game = new MazeGame();MazeFactory factory = new MazeFactory();Maze maze;maze = game.CreateMaze(factory);

But suppose we wanted to create an Enchanted Maze instead. In that case, just make the following changes:

MazeGame game;EnchantedMazeFactory factory;Maze * maze;maze = game.CreateMaze(factory);

Page 16: CHAPTER 3 An Introduction to Design Patterns · An Introduction to Design Patterns 2 Software Development Methods Design Patterns One of the most frequently touted advantages of object-oriented

An Introduction to Design Patterns

16 Software Development Methods

The Java code to do the same task is given by:

MazeGame game = new MazeGame();MazeFactory factory = new EnchantedMazeFactory();Maze maze;maze = game.CreateMaze(factory);

Finally, you could also create a BombedMazeFactory:

MazeGame game;BombedMazeFactory factory;Maze * maze;maze = game.CreateMaze(factory);

The Java code to do the same task is given by:

MazeGame game = new MazeGame();MazeFactory factory = new BombedMazeFactory();Maze maze;maze = game.CreateMaze(factory);

In summary, the abstract factory design pattern allows you to de-couple the creation of a family of objects from the code that actually requires the con-struction in the first place.

Notice that in order for the abstract factory pattern to be effective, all of the object being constructed must refer to a base class from which their many variations are inherited.

Page 17: CHAPTER 3 An Introduction to Design Patterns · An Introduction to Design Patterns 2 Software Development Methods Design Patterns One of the most frequently touted advantages of object-oriented

Software Development Methods 17

An Introduction to Design Patterns

Singleton The Singleton pattern should be used when it is only necessary for one instance of a class to be instantiated within a given thread program. The Singleton will allow the construction of one instance of the class and provide a means for the program to gain access to that instance.

This is not as uncommon as you might think. For example, consider the follow-ing classes:

• Calendar• Abstract Factory

It is unusual for any given program to ever need more than a single instance of either of these classes. As a matter of fact, it may be inefficient from a hardware resource standpoint to allow multiple instances of certain classes. The Singleton pattern can prevent that from happening.

A singleton is its own participant.

The results we gain from using singleton pattern are:

• Controlled access to the single instance.• Reduced name space. This means that we can avoid using global variables.• Ability to refine operations and representation. A singleton class may be sub-

classed and you can allocate the specific kind of singleton you want at run-time.

• Flexible number of instances. Although the singleton pattern only permits a single instance “out of the box” it is simple to re-configure this pattern to per-mit a specific number of instances. One example of this usage is database connection pooling. We may only want a specific number of database connec-tions to be created. Once that limit is reached, we do not allow more to be constructed.

• More flexible than static class methods. Since static class methods cannot be virtual and thus cannot be overridden by derive classes, singleton’s provide flexibility that isn’t available in static classes.

Implementation The general approach to implementing a Singleton is as follows:

• The class constructor is private or protected. This prevents the alloca-tion of the class.

• A static class data member is provided. This will hold the single instance of the singleton class.

• A static class method is provided that returns the static data member.

Page 18: CHAPTER 3 An Introduction to Design Patterns · An Introduction to Design Patterns 2 Software Development Methods Design Patterns One of the most frequently touted advantages of object-oriented

An Introduction to Design Patterns

18 Software Development Methods

A sample C++ implementation of a Singleton is given as:

class Singleton {private:

static Singleton * _instance;protected:

Singleton();public:

static Singleton * Instance();};

Notice that the private _instance member as well as the Instance() method are static which means that they can be called without an actual instance of the class; they exist at the class-level. However, because they are static, we must deal with the data member. Specifically, we must declare and initialize it:

Singleton * Singleton::instance = null;

We must also write the Instance() method such that it allows an initial allo-cation when necessary and otherwise returns the reference to the _instance data member:

Singleton * Singleton::Instance(void) {if (_instance == null) {

_instance = new Singleton();}return (_instance);

}

The corresponding Java representation to the above code is given by:

public class Singleton {private static Singleton _instance = null;

protected Singleton();

public static Singleton Instance(){if (_instance == null) {

_instance = new Singleton();}return (_instance);

}}

Page 19: CHAPTER 3 An Introduction to Design Patterns · An Introduction to Design Patterns 2 Software Development Methods Design Patterns One of the most frequently touted advantages of object-oriented

Software Development Methods 19

An Introduction to Design Patterns

It should come as no surprise that singletons are frequently used to control the number of instances of abstract factories. After all, how many factory objects does one need in order to construct the same set of objects?

For example, suppose we wanted to make the MazeFactory use a single-ton design patterns. We might end up with the following C++ declaration:

class MazeFactory{

private:static MazeFactory * _instance;

protected:MazeFactory();

public:static MazeFactory * Instance(void);

virtual Maze * makeMaze(void) const{ return new Maze(); }

virtual Wall * makeWall(void) 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); }

};

The initialization of the _instance data member would be done as:

MazeFactory * MazeFactory::_instance = null;

Page 20: CHAPTER 3 An Introduction to Design Patterns · An Introduction to Design Patterns 2 Software Development Methods Design Patterns One of the most frequently touted advantages of object-oriented

An Introduction to Design Patterns

20 Software Development Methods

The implementation of the Instance() method would be as follows:

MazeFactory * MazeFactory::Instance(void) {if (_instance == null) {

_instance = new MazeFactory();}return (_instance);

}

We can also provide a Java implementation of this new and improved Maze-Factory class:

public class MazeFactory {private static MazeFactory _instance = null;

protected MazeFactory() { ... }

public static Instance() {if (_instance == null) {

_instance = new MazeFactory();}return (_instance);

}

public Maze makeMaze(){ return new Maze(); }

public Wall makeWall(){ return new Wall(); }

public Room makeRoom(int n){ return new Room(n); }

public Door makeDoor(Room r1, Room r2){ return new Door(r1, r2); }

};

Page 21: CHAPTER 3 An Introduction to Design Patterns · An Introduction to Design Patterns 2 Software Development Methods Design Patterns One of the most frequently touted advantages of object-oriented

Software Development Methods 21

An Introduction to Design Patterns

Using the Singleton MazeFactory

It is now an extremely straightforward proposition to make use of our new fac-tory:

MazeGame game;Maze * maze;maze = game.CreateMaze(*MazeFactory::Instance());

The Java code to do the same task is given by:

MazeGame game = new MazeGame();Maze maze;maze = game.CreateMaze(MazeFactory::Instance());

Notice that we could have gotten even more inventive with the Singleton class. For instance, suppose we wanted to be able to parameterize the type of Maze-Factory that was returned by the Instance() method. With the addition of a few details, this can be arranged:

enum FactoryType { MAZE, ENCHANTED, BOMBED };

class MazeFactory {private:

static MazeFactory * _instance[3];protected:

MazeFactory();public:

static MazeFactory * Instance(FactoryType);

virtual Maze * makeMaze(void) const{ return new Maze(); }

virtual Wall * makeWall(void) 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); }

};

Page 22: CHAPTER 3 An Introduction to Design Patterns · An Introduction to Design Patterns 2 Software Development Methods Design Patterns One of the most frequently touted advantages of object-oriented

An Introduction to Design Patterns

22 Software Development Methods

MazeFactory * MazeFactory::Instance(FactoryType ft) {if (_instance[ft] == null){

switch (ft) {case MAZE:

_instance[ft] = new MazeFactory();break;

case ENCHANTED:_instance[ft] = new EnchantedMazeFactory();break;

case BOMBED:_instance[ft] = new BombedMazeFactory();break;

}}return (_instance[ft]);

}

Java can provide the same functionality:

public interface FactoryType { public static final int MAZE = 0;public static final int ENCHANTED = 1;public static final int BOMBED = 2

}

Page 23: CHAPTER 3 An Introduction to Design Patterns · An Introduction to Design Patterns 2 Software Development Methods Design Patterns One of the most frequently touted advantages of object-oriented

Software Development Methods 23

An Introduction to Design Patterns

public class MazeFactory {private static MazeFactory _instance[3];

protected MazeFactory();

public Maze makeMaze(){ return new Maze(); }

public Wall makeWall(){ return new Wall(); }

public Room makeRoom(int n){ return new Room(n); }

public Door makeDoor(Room r1, Room r2){ return new Door(r1, r2); }

public static MazeFactory Instance(int ft) {if (_instance[ft] == null) {

switch (ft) {case MAZE:

_instance[ft] = new MazeFactory();break;

case ENCHANTED:_instance[ft] =

new EnchantedMazeFactory();break;

case BOMBED:_instance[ft] = new BombedMazeFactory();break;

}}return (_instance[ft]);

}}

Class Exercise Apply the abstract factory pattern to the following simple scenario and provide the associated class diagram:

A company maintains a fleet of vehicles: cars, trucks, and buses. Each car, truck or bus is fueled by either gas, electricity, or diesel.

Please write the names of the members in your group are on a single, legible ver-sion of your diagram and turn it in before leaving. This is not being graded but will be used so I can evaluate your understanding of the material.

Page 24: CHAPTER 3 An Introduction to Design Patterns · An Introduction to Design Patterns 2 Software Development Methods Design Patterns One of the most frequently touted advantages of object-oriented

An Introduction to Design Patterns

24 Software Development Methods