creational design patterns introduction

26 Aug 2019

Notes are based on Design Patters: Elements of Reusable Object-Oriented Software

This weekend was a laptop free zone. The aim for the weekend was to take some real time off, and that's going to be a plan from now on. No screens at the weekend or after 6pm. Yesterday, I spent a few hours lying in the hammock outside in the sun, reading the Design patterns book on my Kindle (which doesn't count as screen time as it's a substitute for books).

I took a bunch of notes. The first couple of chapters were pretty difficult for me to understand, but after reading them three or four times managed to get a better idea each time. I used to feel so bad when I didn't understand concepts in tech books, but can now understand books that I really struggled with a year or so ago so know that I will get this in my own time, and it's going to be SO MUCH FUN, yayy!

Creational patterns

There are three main categories of Design Patterns: Creational, Structural and Behavioural. Starting with the Creational patterns first. Each of the Design patterns can be applied to classes, or instances of those classes (objects)

Class Creational Patterns uses inheritance to vary the class that is intantiated. While Object Creational Patterns will delegate instantiation to other objects.

2 x themes

  1. They encapsulate knowledge about which concrete classes the system uses
  2. They hide how instances of these classes ore created and put together

Concrete classes refer to classes that are not abstract classes. Abstract classes are like a bluprint of things you want your class to contain. A concrete class inherits from the abstract class, and MUST contain the behaviours that are specified in the abstract class blueprint. The concrete class can have additional behaviours too.

The main purpose of creational classes is to remove explicit references to concrete classes from code that needs to instantiate them. So we don't say, we need to create an instance of this specific class in our code. Instead we say, we are going to create an instance of a non-specific class that can be substituted for other classes that use the same interface as each other (or part of the same interface). The interface is made up of the methods that we are calling (messages we are senting to) the object that we are using the class to create an instance of. As long as we use classes that can understand those method calls, then we can substitute the class with any of those classes instead of locking in a single class when we are defining which one should be instantiated at run time.

Problem Statement

To illustrate the how creational patterns are used, the authors provide an example of creating a Maze with the component objects (Room, Door and Wall) that make up the maze. The y start with code that does this without using any of the creational patterns. The subsequent chapters then show you how this code is refactored to use the creational patters.

The following code is written in C++, and represents the class responsible for creating a Maze with two rooms. Below this code I will explain what each line is doing (mostly for my own benifit as someone not familiar with this language yet). Then I'll include the problems associated with hard-coding references to the concrete classes as mentioned in the chapter. After that, I'll include a brief summary of how each of the creational patterns approach removing the referenced to concreate classes.


Maze* MazeGame:: CreateMaze() {
  Maze* aMaze = new Maze;
  Room* r1 = new Room(1);
  Room* r2 = new Room(2);
  Door* theDoor = newDoor(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;
}

Code explanation (for my own benefit)

I asked for help understanding the 'Maze*' pointer symbols and the difference between the first and second lines. It was explained to me three or four times in different ways until I finally understood it, because there is a lot going on there behind the scenes. So will start with the analogy that was told to me first.

We are planning to return the address of the cupboard that is going to hold a cup of coffee. This plan is written down as 'Coffee*'. In this plan, we don't care how the coffee is made, just that there will be a cupboard that holds a cup of coffee. Then we make a simple cup of coffee (coffee and hot water) and place it into a cupboard. Before we return the location of that coffee, we first add some milk and sugar to it. After the coffee has been made just how we like it, we return the address. We have now fulfilled our plan to return the address that holds a cup of coffee.

Following this analogy here is what the code above is doing.

Problems

In this CreateMaze method, we have explicitly said that we are going to be using instances of specific classes (Room, Wall and Door). The problem with this is that we might want to create a new maze in the future with different components. We might use a different type of door that needs a spell to be opened, or a room that holds magical objects etc. Howeve r, in order to be able to do this we would have to rewrite this method to explicitly state that we want to use these other types of objects.

Creational patterns allow you to easily substite classes so that you don't have to be explicit about which exact ones you are going to be using every time. This makes it easier t o create new Mazes that might vary from the one you created initially.

Five Creational patterns

There are five different creational patterns. Each of them will change the CreateMaze code above so that there are no longer explicit references to the classes that we are instantiating. There are subtle differences between how each of them do this, and they all have their own advantages and trade-offs. I'll briefly cover how they will solve this problem below, based on the introduction chapter, and not the individual chapters for each of these patterns. So nuances will be missing in this overview.