9 Software design patterns
9.1 Introduction
In this workshop, we will look at design patterns, and their application in refactoring. A software design pattern describes a general, reusable solution to a commonly occurring problem in a specific design context. They’re often useful when designing new pieces of software as they both allow us to reuse best practice from prior experience, and provide a means to discuss the design with others (a shared vocabulary). However, design patterns can be equally useful when refactoring existing codebases.
The workshop builds on techniques given in previous workshops for working with large codebases, in particular extending those in Workshop 8 for refactoring existing code. In this workshop you will:
- Be introduced to 6 of the 23 Gang of Four (GoF) Design Patterns (Gamma et al. 1994)
- Refactor a small existing code base to apply Behavioural, Structural and Creational patterns
We’ll be assuming that, after this workshop, you are capable of carrying out the following tasks for yourself, without needing much guidance:
- Identify portions of existing codebases that could be improved through the application of Design Patterns
- Describe a refactoring using a design pattern vocabulary and, where appropriate, supporting UML
- Apply design patterns to an existing codebase
As in prior workshops, there will be scope to work through the tasks at your own pace – in particular, each of the three workshop exercises is divided into multiple stages that address first one design pattern, and then a second. You should (at a minimum) aim to have completed all tasks related to the first design pattern of each exercise.
9.2 Workshop Exercise 1 - Behavioural Patterns
This first section of the workshop focuses on applying the two Behavioural patterns introduced: Strategy, and State.
For this exercise, you’ll be working with a small-scale Java codebase that’s loosely inspired by some classes in the Stendhal codebase. For this first exercise you’ll be focussing on a set of classes that represent pets. The main classes and their members can be represented in a UML class diagram shown in figure 9.1

Figure 9.1: Pets, Cats, Goats and Magic Dragons
In this workshop you’ll be extending and then refactoring the codebase to explore how behavioural patterns can simplify the process of adding new functionality, and can remove the need for duplicate code.
9.2.1 Exercise 1a - The Strategy Pattern
In this part of the exercise we’ll be focusing on the Strategy pattern. You will modify the code in five stages:
- Add a new
PetclassCuddlyToythat requires new algorithms for the growth, feeding, hunger, and crying. - Consider how one might use sub-class/super-class relationships to avoid duplicate code.
- Implement an abstract
GrowthStrategythat provides method signatures for growth-related algorithms. - Implement the three concrete implementations of
GrowthStrategyencountered so far. - Modify the existing
Petclasses to use the newly created strategy classes.
9.2.1.1 Stage 1 - Add a new Pet class
You’ve been asked to add a new Pet, CuddlyToy, for players that (for example) have allergies or just don’t want the effort of looking after a real-life creature. The requirements for CuddlyToy are as follows:
- A
CuddlyToyshould not grow, they areADULT_SIZEat instantiation. - A
CuddlyToydoes not eat, and should not get hungry. - A
CuddlyToysqueaks, its cry is generated by a plastic squeaker
[ACTION] Implement a new Pet subclass that complies with the above requirements, and modify PetDriver.java to demonstrate your new Pet subtype.
9.2.1.2 Stage 2 - Design sub-class/super-class relationships to avoid duplicated code
It’s clear that many of our Pets have quite different algorithms for growth. Some, like Goats and Cats grow steadily, increasing by a fixed amount over a constant time interval. Others, like MagicDragons, increase by a fixed amount but at irregular intervals – their growth stagnates for a while and then they undergo a growth spurt. Some Pets, like CuddlyToys, don’t grow at all.
If we wanted to introduce more Pet types, we could quickly end up having to duplicate the code for steady, irregular or no growth across multiple Pet subclases. Alternatively, we could add layers of subclassing shown in figure 9.2

Figure 9.2: Possible subclasses of Pet
However, this could quickly become difficult to manage, and doesn’t always avoid duplicate. For example, suppose you’re now asked to add a new Bird subtype. Birds can fly (like Dragons) but grow steadily (like Cats and Goats). The resulting class structure might look something like that shown in figure 9.3

Figure 9.3: A badly designed hierarchy
So, we’ve now potentially duplicated our steady growth code in two superclasses SteadilyGrowingGroundPet and SteadilyGrowingFlyingPet, and some new code for flying behaviours in two superclasses SteadilyGrowingFlyingPet and RandomlyGrowingFlyingPet. This definitely isn’t great design.
9.2.1.3 Stage 3 - Introduce a GrowthStrategy
A Strategy pattern defines a encapsulates a family of interchangeable algorithms – here, our interchangeable algorithms describe different patterns of growth.
The first step in refactoring to a Strategy will be to create an abstract class GrowthStrategy.
[ACTION] Create a new GrowthStrategy class, with abstract method signatures for canGrow() and Grow().
9.2.1.4 Stage 4 - Implement concrete growth strategies
You now need to create concrete implementations of your GrowthStrategy class, each representing a different growth algorithm. So far, we’ve encountered three growth algorithms:
- Steady growth – Grows by a fixed amount every time the grow method is called.
- Random growth – Grows by a fixed amount some random subset of times that the grow method is called.
- No growth – Does not grow, even when the grow method is called.
[ACTION] Create three subclass implementations of GrowthStrategy, one for each of the growth algorithms encountered so far.
9.2.1.5 Stage 5 - Modify the codebase to use our GrowthStrategy
Now we have a selection of implemented GrowthStrategy classes, we need to modify the Pet subclass to utilise these new classes. To do this, we’ll add an attribute growthStrategy of type GrowthStrategy to the Pet class. We’ll also need to add a set method for the new attribute, and modify the existing canGrow() and grow() method in Pet and it’s subclasses to make calls to the new strategies.
[ACTION] Make the remaining code changes needed to have Pet and its subclasses use GrowthStrategy. This should now mean that there is no special-case grow() implementation in MagicDragon and CuddlyToy. Verify that PetDriver.java still behaves as expected.
The UML diagram in figure 9.4 should be a good representation of your codebase at the end of this migration task.

Figure 9.4: Your codebase should look something like this at the end of this migration task
9.2.2 Exercise 1b - The State Pattern
In this part of the exercise we’ll be focusing on the State pattern. You will continue to modify the Pet codebase.
In this task, you should look to apply the State pattern to store attributes related to hunger, and algorithms that depend on those attribute values.
Note that this is the extension/secondary task for “Exercise 1 - Behavioural Patterns”. Detailed instructions are therefore not provided, but a suggested approach might break the modification down into the following four further stages:
- Identify hunger states and their dependant behaviours.
- Implement an abstract
HungerStatethat provides method signatures for dependant behaviours. - Implement a concrete implementations for each of the hunger states identified previously.
- Modify the existing
Petclasses to use the newly created state classes
You may find it helpful to make brief UML sketches as needed as you refactor the code towards the State pattern.
9.3 Workshop Exercise 2 - Structural Patterns
This first section of the workshop focuses on applying the two Structural patterns introduced: Composite, and Adapter.
For this exercise, you’ll be working with a set of classes that represent habitats – places that pets might want to live. The main classes and their members can be represented in a UML class diagram in figure 9.5

Figure 9.5: Subclasses of Habitat
In this workshop you’ll be extending and then refactoring the codebase to explore how structural patterns can simplify the process of adding new functionality, and can remove the need for duplicate code.
9.3.1 Exercise 2a - The Composite Pattern
In this part of the exercise we’ll be focusing on the Composite pattern. You will modify the code in 3 stages:
- Add new
Habitatclasses,Cave,Field, andMuddyPuddle - Modify
Habitatsuch that it can (optionally) contain a number of childHabitatobjects. - Modify the
describe()andgetOccupants()methods to include the values of child objects.
9.3.1.1 Stage 1 - Add new Habitat classes
You’ve been asked to add some new Habitat classes to represent more specific places that Pets might choose to spend time.
The current description for MythicalCaveSystem already indicates that the cave system is actually composed of three separate Caves.
Likewise, the Farm is described as containing multiple fields and a barn.
You’ve been asked to add three specific new Habitat classes:
-
Cave- A single cave for dragons to hide in. -
Field- A field with grass that goats might eat. -
MuddyPuddle- A patch of muddy water – goats love splashing in puddles.
[ACTION] Implement three new Habitat subclass as above, and modify HabitatDriver.java to demonstrate your new Habitat subtypes.
9.3.1.2 Stage 2 - Modify Habitat to contain child Habitat objects
We already know that MythicalCaveSystem contains three Caves, and that Farm contains a Field. We’re going to use the Composite pattern to make this relationship an integral part of our class structure.
To start this refactoring, you’ll need to modify Habitat to have a list of children; children should be of type Habitat.
[ACTION] Modify the Habitat class to add the new element.
[ACTION] Create new methods to add, remove and get children to a Habitat.
[ACTION] Modify HabitatDriver to demonstrate that multiple Caves objects can be added as a child of a MythicalCaveSystem, and that a Field can be added as the child of a Farm.
[ACTION] Modify HabitatDriver to demonstrate that an instance of MuddyPuddle can be added as a child of the Field (which is itself a child of Farm).
9.3.1.3 Stage 3 - Modify Habitat to call child methods
The final stage of our refactoring is to make sure that the descriptions of each Habitat are as complete as possible, and that the occupancy counts are correct (i.e. they include occupants in any part of the Habitat). To do this, we need to make sure that the describe() and getOccupants() of Habitat recursively call the same methods on any children.
[ACTION] Modify describe() to recursively call childHabitat.describe() for every childHabitat in the list of children for this habitat. You will need to store the result and build a new formatted description string in the parent.
[ACTION] Modify getOccupants() to recursively call childHabitat.getOccupants() for every childHabitat in the list of children for this habitat. You will need to store the result to build one complete list of every Pet in parts of the top-level Habitat.
[ACTION] Modify HabitatDriver to demonstrate that your new describe() and getOccupants() methods work as expected. In particular you should confirm that:
- A call to
aMuddyPuddle.describe()shows only the description for theMuddyPuddle. - A call to
aField.describe()shows the description for theFieldand theMuddyPuddle. - A call to
theFarm.describe()shows the description for theFarm, theFieldand theMuddyPuddle.
Likewise, you should check calls to getOccupants() for each of the above, and check both describe() and getOccupants() for theCaves and aCave.
[OPTIONAL EXTRA] Modify removeOccupant() to remove an Occupant from this child Habitats if they aren’t found in the parent.
The UML diagram in figure 9.6 should be a good representation of your codebase at the end of this migration task.

Figure 9.6: Your codebase should look something like this at the end of this migration task
9.3.2 Exercise 2b - The Adapter Pattern
In this part of the exercise we’ll be focusing on the Adapter pattern. You will continue to modify the Habitat codebase.
In this task, you should look to apply the Adapter pattern to make a legacy class FieryMountains.java available as a possible Habitat.
FieryMountains was implemented many years ago for a previous game but has lots of neat graphics that the team want to reuse. You should use the Adapter pattern to FieryMountains to be used as is, as a new Habitat. You absolutely must not modify FieryMountains.java, and it must be used in your final solution (i.e. you can’t just copy and paste a few values out and then just ignore it).
Note that this is the extension/secondary task for “Exercise 2 - Structural Patterns”. Detailed instructions are therefore not provided, but a suggested approach might break the modification down into the following four further stages:
- Create a new Java stub
FieryMountainsAdapterthat extends Habitat and stores a newFieryMountainsinstance as one of its attributes. - Write a new implementation for
FieryMountainsAdapter.describe(), that complies with the signature provided for this method inHabitatand calls relevant functionality fromFieryMountains. - Modify
HabitatDriverto demonstrate that fiery mountains can be added to theArrayListof Habitats, and thatPetinstances (maybe aDragon?) can be added as an occupant ofFieryMountains.
You may find it helpful to make brief UML sketches as needed as you refactor the code towards the Adapter pattern.
9.4 Workshop Exercise 3 - Creational Patterns
This first section of the workshop focuses on applying the two Creational patterns introduced: Factory Method, and Singleton.
These two patterns should be more familiar to you, from your experiences in this and other courses. For example, you’ve previously looked at Stendhal’s own Singleton class RPWorld in one of the early workshops.
For this exercise, you’ll be working with the Pet and Habitat classes you’ve already seen. This time we’re using these classes together as part of a Tamagotchi application – a simple text based application that lets users look after a virtual pet for a while.
In this workshop you’ll be extending and then refactoring the codebase to explore how creational patterns can allow users to control instantiation of Pets and Habitats.
9.4.1 Exercise 3a - The Factory Method
In this part of the exercise we’ll be refactoring the Factory Method to instantiate different Pet and Habitat classes at runtime1
This is a much simpler change than previous changes, and can most likely be achieved in 3 stages:
- Add a new
PetCreatorclass that createsPetobjects in response to aStringparameter. - Add a new
HabitatCreatorclass that createsHabitatobjects in response to aStringparameter. - Modify the
Tamagotchiclass to use the new classes, passing user input in as theStringparameters.
You should now be able to carry out these changes without the more detailed instructions of previous exercises.