Week 2 - Creational Patterns: The Abstract Factory, Builder and Prototype Patterns

Lecture recording (Tuesday 10 September, 2024) here.

Lab recording (Thursday 12 September, 2024) here.

Introduction

We continue our study of creational patterns. The abstract factory provides an interface for creating families of related objects without specifying their concrete classes.The Builder pattern separates the construction of a complex object from its representation, allowing the same construction process to create different representations. The Prototype pattern creates new objects by copying an existing object, without coupling the code to the specific classes of those objects.

Videos

The Abstract Factory PatternAbstract Factory Design Pattern
The Abstract Factory Pattern Explained and Implemented
The Builder Design PatternBuilder Design Pattern Explained in 10 Minutes
The Builder Pattern Explained and Implemented in Java
The Prototype Design PatternPrototype Design Pattern Tutorial
The Prototype Pattern Explained and Implemented in Java

Assignment(s)

Assignment 1 - The Retail Store

The Abstract Factory Design Pattern

The Rationale

The Abstract Factory design pattern is used to provide an interface for creating related or dependent objects, without specifying their concrete classes. The main rationale behind the pattern is to provide a way to create families of related objects that can work together without specifying their concrete classes. The objects are created through a common interface or abstract class, which is implemented by a set of concrete classes. This allows the client code to create objects without knowing their concrete classes, and it ensures that the created objects are compatible with each other and can work together.

The UML

Here is the UML diagram for the abstract factory design pattern:
The Abstract Factory Pattern
Here are the components of the abstract factory design pattern:

  1. Abstract Factory: This is an abstract class that defines the interface for creating families of related products. It has factory methods for creating ProductA and ProductB objects.
  2. ConcreteFactory1, ConcreteFactory2: These are concrete classes that implement the AbstractFactory interface. They create specific families of related products.
  3. Abstract ProductA, Abstract ProductB: These are abstract classes that defines the interface for the products that are created by the factory.
  4. ProductA1, ProductA2, ProductB1, ProductB2: These are concrete classes that implement the Abstract Product classes.
  5. Client: The relationship between the Abstract Factory and the Abstract Products represent the fact that the factory methods in the abstract factory create objects that implement the Abstract Product interfaces.

Code Example - Abstract Factory

The following is sample code for the abstract factory:
C++: AbstractFactory.cpp.
C#: Program.cs.
Java: AbstractMain.java.
Python: Python.py.

Common Usage

The following are some common usages of the abstract factory pattern:

  1. The abstract factory pattern can be used to create a set of UI components that are compatible with different platforms. The abstract factory can provide an interface for creating UI components such as buttons, text fields, labels, and so on, and different concrete factories can be implemented for different platforms like Android, iOS, or web.
  2. The abstract factory pattern can be used to create objects that interact with databases. An abstract factory can define an interface for creating database objects such as connections, statements, and result sets, and concrete factories can be implemented for different types of databases like MySQL, PostgreSQL, or MongoDB.
  3. The abstract factory pattern can be used to create different types of game objects. An abstract factory can define an interface for creating game objects such as characters, weapons, enemies, and so on, and concrete factories can be implemented for different game genres like first-person shooters, platformers, or role-playing games.
  4. The abstract factory pattern can be used to create different types of documents such as PDF, HTML, or Word. An abstract factory can define an interface for creating document objects such as headers, footers, and body text, and concrete factories can be implemented for different document types.

Code Problem - GUI Application

We wish to write graphics code that can run on any machine. In this use-case scenario, the machines are Windows and Mac. The following program uses the abstract factory pattern in generating graphics for both windows and mac machines.

Widget.h, the abstract product,
Button.h, the concrete product,
Textbox.h, the concrete product,
Theme.h, the abstract theme,
LightTheme.h, the concrete theme,
DarkTheme.h, the concrete theme,
GUIFactory.h, the abstract gui factory,
WindowsFactory.h, the concrete windows factory ,
MacFactory.h, the concrete mac factory,
GUIMain.cpp, the main function.

The Builder Design Pattern

The Rationale

The Builder software design pattern is a creational pattern that aims to separate the construction of complex objects from their representation, allowing the same construction process to create different representations. This is achieved through creating complex objects step by step while keeping the creation process flexible and modular. This makes the pattern useful in scenarios where different clients may need different representations of the same object, or where the object itself may have multiple representations depending on the context in which it is used. By breaking down the creation process into smaller, more manageable steps, the pattern makes it easier to test and debug the code, as well as to reuse and extend the creation process in the future.

The UML

Here is the UML diagram for the Builder pattern:
The Builder Pattern
Here are the components of the Builder design pattern:

  1. Director: This component is responsible for managing the construction process, which involves invoking the builder's methods to create the final object.
  2. Builder: This component defines the interface for creating parts of the final object. It typically includes methods for creating individual components of the final object and a method for returning the final object.
  3. Concrete Builder: This component implements the Builder interface and provides concrete implementations of the builder methods. There are many Concrete Builder classes derived from Builder, each will build the product a certain way.
  4. Product: This component represents the final object being created by the builder. It typically includes attributes and methods that define the functionality of the final object.

Code Example - Pizza Builder

In the following example the Pizza class represents the final object being created, and the PizzaBuilder abstract class defines the interface for creating parts of the final object. The concrete builders are HawaiianPizzaBuilder and SpicyPizzaBuilder. The Cook class is the director. The code is written in four languages:
C++: Builder.cpp.
C#: Builder.cs.
Java: Builder.java.
Python: Builder.py.

Common Usage

Some common uses of the Builder pattern in software industry include:

  1. Creating complex configuration objects: The Builder pattern is particularly useful when creating objects that have many different configuration options, and where not all options are needed or required.
  2. Generating test data: The Builder pattern can be used to generate test data with complex object structures and configurations. By using a builder to create test data, it is possible to ensure that the data is consistent and valid, while also allowing for easy modification of the data to create different test cases.
  3. Creating user interfaces: The Builder pattern can be used to create user interfaces with complex layouts and configurations. By using a builder to create the interface, it is possible to simplify the process of laying out and configuring the interface, while also allowing for easy modification and customization.
  4. Generating reports: The Builder pattern can be used to generate reports with complex structures and configurations.

Code Problem - Computer Builder

We want to build one of two types of computer - a low end computer and a high end computer. The low end computer will have an Intel Core i5-750 cpu, 16Gbytes of RAM, two hard disks (500GB Seagate SATA HDD and 1 TB Samsung 980 HDD), a graphics card (EVGA - XR1 Pro Capture Card), and requires a 500W power supply. The high end computer will have an Intel Core i9-11900K cpu, 32 GBytes of RAM, two hard disks (1 TB NVMe SSD and 4 TB HDD), a graphics card (NVIDIA GeForce RTX 3090), and requires a 1000W power supply.

The user will choose which type to build. Since the type is not known at compile time, both the low and and high end computers will have to be derived from a common base class so they can be treated the same way. We need a computer system director to direct the build process. And we need the computer system itself.

Code that implements the computer builder can be found at:
ComputerSystemBuilder.h, an abstract base class,
LowEndComputerSystemBuilder.h, derived from ComputerSystemBuilder,
HighEndComputerSystemBuilder.h, derived from ComputerSystemBuilder,
ComputerSystemDirector.h, directs the build process,
ComputerSystem.h, the computer system,
ComputerBuilderMain.cpp, the main function.

The Prototype Design Pattern

The Rationale

The Prototype software design pattern is used to create new objects by cloning existing ones, rather than by creating new objects from scratch. The primary rationale behind this pattern is to reduce the cost of object creation. When your objects have dozens of fields and hundreds of possible configurations, cloning them might serve as an alternative to subclassing. An analogy to cloning is cell division:
Cell division

The UML

Here is the UML diagram for the Builder pattern:
The Prototype Pattern
Here are the components of the Builder design pattern:

  1. Prototype Interface: This is an interface that declares a clone() method which is used to create a copy of the prototype object.
  2. Concrete Prototype Classes: These are concrete implementations of the prototype interface that define their own implementation of the clone() method.
  3. Client: The client is the code that creates new objects by cloning the prototypes. The client uses the clone() method to create a new instance of the prototype, and then can modify the cloned object as necessary.

Code Example - Writing Tool

In the following example, we have two concrete prototype classes, Pen and Pencil, which both implement the WritingTool interface. Each class has its own implementation of the write() method, which prints out a message indicating which writing tool is being used to write the text.

We first create a prototype object of a Pen, clone it to create a new Pen object, and use it to write some text. We then create a prototype object of a Pencil, clone it to create a new Pencil object, and use it to write some text. Sample code is as follows:
C++: WritingTool.cpp.
C#: WritingTool.cs.
Java: WritingToolMain.java.
Python: Python.py.

Common Usage

Some specific examples of the use of the Prototype pattern in software development include:

  1. Creating multiple instances of a complex graphic object, such as a chart or graph.
  2. Creating multiple instances of a complex network or database connection object, with different credentials or configurations.
  3. Creating multiple instances of a complex game character or enemy, with different attributes or behaviors.
  4. Creating multiple instances of a complex document, such as a report or presentation, with different styles or formatting.

Code Problem - Shape Cache

We want to be able to pull up one or more of several predefined shapes without having to invoke a constructor at run-time. In graphics programs, there may not be time to construct a complex graphical image while the program is running. Rather, we need these predefined shapes to have already been created for us. The predefined shapes in this example will be Rectangle with dimensions 10x20, a Rectangle with dimensions 20x40, a Rectangle with dimensions 40x80, a Circle with radius 5, a Circle with radius 10, and a Circle with radius 20.

Code that implements the prototype design pattern in this implementation can be seen at:
Shape.h, abstract base class so all shapes can be treated the same,
Rectangle.h, derived from Shape,
Circle.h, derived from Shape,
ShapeCache.h, a cache of predefined shapes,
ShapeMain.cpp, the main function.

Workshop Sample Code

Common themes in design patterns

Design patterns are used by experts to make their designs more flexible and reusable, so in studying them you tend to see:

Create a design pattern that separates the concerns of an application into three interconnected components: the Model, the View, and the Controller. Each component has its own distinct responsibilities, which helps in achieving modularity and maintainability.

  1. Model: The Model represents the application's data and business logic. It encapsulates the data and provides methods to manipulate and access it. The model component does not depend on the view or the controller and can be reused across different user interfaces.
  2. View: The View is responsible for rendering the user interface and presenting the data from the model to the user. It displays the information and provides a way for the user to interact with the application. The view should ideally be passive and not contain any business logic.
  3. Controller: The Controller acts as an intermediary between the Model and the View. It handles the user's input, processes the requests, and updates the model accordingly. It also updates the view based on changes in the model. The controller component is responsible for managing the flow of the application.

See the The MVC Design Pattern. A solution can be seen at: UserModel.h, UserView.h, UserController.h and MVC.cpp.

The Pizza Builder Revisited

Let us build a pizza without using the builder design pattern. We have a class for a Pizza which can be HawaiianPizza or SpicyPizza. We have a Cood who is going to build this pizza for us. The following code shows how to build a pizza without the design pattern. Do you find this code awkward?
Pizza.h, the Pizza base class,
HawaiianPizza.h, derived from Pizza,
SpicyPizza.h, derived from Pizza,
Cook.h, the Cook class, and
Pizza2.cpp, the main function.
As you can see from the code, an enumeration is required to replace the builder class.