An Introduction to Design Patterns

By now you should understand the importance of a good OO design! Good OO designs should be reusable, extensible and maintainable. In fact, useful software constantly changes to add new features, extend existing features, fix bugs, improve performance etc. Software systems with a poor OO design are very hard to maintain and even performing a simple change might take a lot of time and effort. Do you know that software developers spend more time reading the code than actually writing it?

Do you think that crafting good OO designs is difficult?… don’t worry Design Patterns are here to help you!

Software design patterns describe common (and very successful) ways of building software. More specifically, a software design pattern is a general, reusable solution (template) to a commonly occurring problem in software design.



Let’s see a real world example: The “door” pattern

Problem:

  • We want a portal between two spaces
  • Must be able to open and close

Solution: Build a door!

Doors have multiple components: lock, handle, frame etc…

The design pattern “door” specifies how these components interact to solve our problem. The “door” design is reusable, in fact the implementation can be very different!

           



In this course we are interested in object-oriented design patterns, which show relationships and interactions between classes or instances.

There are 23 main OO Design patterns, which can be classified into three main categories:

  • Creational Patterns (5) provide ways to create instances while hiding the creation logic, instead of instantiating objects directly using the new operator. This gives the program more flexibility in deciding which instances need to be created.

  • Structural Patterns (7) deal with class and object composition. The concept of inheritance is used to compose interfaces and define ways to compose objects to obtain new functionality.

  • Behavioural Patterns (11) deal with communication between objects.

The chart below shows all of the 23 patterns.

We will cover only few of the most used and important. You are of course encouraged to study more about them in the future. Indeed, design patterns are widely used, and often software developer job interviews have always a couple of questions about them.

There are key advantages of using OO design patterns.

  • They will make your life easier by not reinventing the wheel, design patterns are solution to common OO problems.
  • They will improve your OO skills.
  • OO design patterns are widely used, you will be able to recognizes them if you read code.
  • It is easier to communicate software design ideas if developers have a shared vocabulary. Design patterns will help with that.
  • OO design patterns are not specific to a certain programming language, once you learn them you can use them with any OO programming language (e.g., C#, Ruby, Python, TypeScript)



Some History

In 1977, Design Patterns originated as an architectural concept in the book “A Pattern Language” by Christopher Alexander. He defined patterns as “successful solutions to problems” in the context of building.

In 1987, Ward Cunningham and Kent Beck leverage to idea and applied it to OO programs.

In 1994, the book “Design Patterns: Elements of reusable object-oriented software”, written by four authors (Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides) also called the “Gang of Four”, proposed 23 (classic) design patterns for OO programs.






Edit Caution!

Do not abuse design patterns! Design patterns do not magically improve the design and quality of your code, they might even make it worst if used when are not needed.

It is important to identify the scenarios and problems which the patterns are meant to address. Design Patterns are solutions for common problems, if your program does not suffer from the problem that a design pattern is meant to address do not use that design pattern!






Factory (Creational)

The Factory Design Pattern is one of the most used design patterns in Java. It is very useful when you need to provide high flexibility for your code.

Problem:

In OOP we often have to create different instances o similar classes. In such cases, we want to have highly maintainable code so that when we want to add a new class type, we just want to change the code in one place.

For example, let’s assume that you have opened a Coffee Shop and you only make espressos. You implemented an automated system to make coffee. The code looks like this:


public class Main {

    public static void main(String[] args) {

        EspressoCoffee espressoCoffee = new EspressoCoffee();
        espressoCoffee.grindCoffeeBeans();
        espressoCoffee.make();
        espressoCoffee.serve();
    }

}


This is the EspressoCoffee class:

public class EspressoCoffee {

	public void grindCoffeeBeans() {
		System.out.println("Use espresso roast coffee, about 9 grams for a single espresso shot");
	}

	public void make() {
		System.out.println("Place the portafilter in the espresso machine and press the button to pull the shot.");
	}

	public void serve() {
		System.out.println("Espresso can only be served in small ceramic cups");
	}

}


As your coffee shop gets popular, you want to add more types of coffee: Cappuccino and Americano. However, adding a new coffee to the program is not that simple if the rest of the code is already coupled to the EspressoCoffee type. Adding new coffee types into the program would require making changes to the entire codebase. For example, something like this:

switch (coffeeType) {

    case "E":
        EspressoCoffee espresso = new EspressoCoffee();
        espresso.grindCoffeeBeans();
        espresso.make();
        espresso.serve();
        break;

    case "A":
        AmericanoCoffee americano = new AmericanoCoffee();
        americano.grindCoffeeBeans();
        americano.make();
        americano.serve();
        break;

    case "C":
        CappuccinoCoffee cappuccino = new CappuccinoCoffee();
        cappuccino.grindCoffeeBeans();
        cappuccino.make();
        cappuccino.serve();
        break;

}


Moreover, if later you decide to add another type of coffee, you will probably need to make all of these changes again. As a result, you will end up with bad code, riddled with conditionals that switch the program behavior depending on the type of coffee.
Solution: The Factory pattern can come to the rescue!

This pattern delegates the responsibility of initializing a class to a particular factory class by creating a type of virtual constructor.

First, let’s create a common interface for all types of Coffee. Note that you could also create an abstract class.

public interface Coffee {

	public void grindCoffeeBeans();
	public void make();
	public void serve();

}


Then, let’s create the coffee classes that implement this interface.


public class EspressoCoffee implements Coffee {

	@Override
	public void grindCoffeeBeans() {
		System.out.println("Use espresso roast coffee, about 9 grams for a single espresso shot");
	}

	@Override
	public void make() {
		System.out.println("Place the portafilter in the espresso machine and press the button to pull the shot.");
	}

	@Override
	public void serve() {
		System.out.println("Espresso can only be served in small ceramic cups");
	}
}

public class AmericanoCoffee implements Coffee {

	@Override
	public void grindCoffeeBeans() {
		System.out.println("Use espresso roast coffee, about 6 grams for an americano");
	}

	@Override
	public void make() {
		System.out.println("add 100ml of hot water");
	}

	@Override
	public void serve() {
		System.out.println("Americano can only be served in big glasses");
	}
}


public class CappuccinoCoffee implements Coffee {

	@Override
	public void grindCoffeeBeans() {
		System.out.println("Use espresso roast coffee, about 3 grams for a cappuccino");
	}

	@Override
	public void make() {
		System.out.println("add 100ml of milk, add the foam");
	}

	@Override
	public void serve() {
		System.out.println("Cappuccino can only be served in big ceramic mugs");
	}
}


Then we create a Factory class and method to create the coffee instance based on the type (“A”, “C”, or “E”). The method creataCoffee returns a Coffee object with an implementation that depends on the coffee type given as a parameter.

public class CoffeeFactory {

	public static Coffee createCoffee(String type) {
		switch (type) {
		case "E":
			return new EspressoCoffee();

		case "A":
			return new AmericanoCoffee();

		case "C":
			return new CappuccinoCoffee();

		default:
			System.err.println("wrong coffee type");
			System.exit(0);
		}
		return null;
	}
}


Then, every time we want to create a coffee instance, we will use the Factory method like this:


Coffee coffee = CoffeeFactory.createCoffee("A"); // this will create an Americano
coffee.grindCoffeeBeans();
coffee.make();
coffee.serve();


In the meantime, this is the final result of what we developed above:



Edit Get your hands dirty!

A bank offers credit cards to its customers. They offer three types of credit cards: Silver, Gold, and Platinum cards. Each card type has a different credit limit: Silver NZD 1,000, Gold NZD 2,000, and Platinum NZD 30,000. The type of issued card depends on the yearly income of the customers. Platinum for an annual salary greater than NZD 200,000. Gold for an annual salary from NZD 100,000 to NZD 199,999. For annual salaries of 99,999 or less, the Bank issues a Silver card. The Bank is planning to add more types of cards in the future. Help the Bank to make their software easier to maintain by applying the Factory design pattern.

  • Create an interface CreditCard. The classes PlatinumCard, GoldCard, and SilverCard should implement it.
  • Create a CreditCartFactory class, with a static method createCard(int) following the Factory design pattern.
    BONUS Change the interface CreditCard to an abstract class. Is it better if CreditCard is an interface or an abstract class? Why?







Builder (Creational)

Problem: In OOP we often have classes holding some data that we are setting and later accessing. If we have many instance fields, things can get a bit tedious!

For example, let’s consider the following Pizza class:

public class Pizza{
    private int size;           // mandatory
    private boolean onion;      // optional
    private boolean cheese;     // optional
    private boolean olives;     // optional
    private boolean tomato;     // optional
    private boolean mushroom;   // optional
    private boolean ham;        // optional
    private int sausageCount;   // optional

We have one mandatory attribute (size), and seven optional attributes. The constructor would be:

public Pizza(int size, boolean onion, boolean cheese,
            boolean olives, boolean tomato,
            boolean mushroom, boolean ham, int sausageCount){

    this.size = size;
    this.onion = onion;
    this.cheese = cheese;
    this.olives = olives;
    this.tomato = tomato;
    this.mushroom = mushroom;
    this.ham = ham;
    this.sausageCount = sausageCount;
}

This is very error prone: the parameters might easily be mixed up by the developers as many are of the same type! Seven parameters are not even mandatory. But what if we have new ingredients in the future? Should this constructor be extended with even more parameters to cater for more ingredients? If we do so, we had to change all the previously-written code that used the (old) constructor to add the extra parameter(s)!

Sure, one possible solution is to use a default constructor and then use setter methods to set the optional parameters. For example, something like this:

   Pizza pizza = new Pizza();
   pizza.setSize(10);
   pizza.setTomato(true);
   pizza.setSausageCount(2);
   pizza.setHam(true);

But we still might not want this… what if a developer forgets to set the mandatory field? What if we want that the Pizza is an immutable instance, meaning that once it is created it should not be able to change its state?


Solution: Use the Builder design pattern: the main idea is to separate mandatory from optional parameters, and to move the construction logic out of the object’s class into a separate static inner class referred to as a “builder” class:

  • The Builder class has a constructor only for mandatory parameters, and setter methods for all the optional parameters.
  • In addition, there is a build() method that glues everything together and returns a complete instance.

Consider this:

// Pizza class:
public class Pizza {
    ...

    // Pizza's inner static Builder class:
    public static class Builder {

        private int size;                 // mandatory
        private boolean onion = false;    // optional with default value
        private boolean cheese = false;   // optional with default value
        private boolean olives = false;   // optional with default value
        private boolean tomato = false;   // optional with default value
        private boolean mushroom = false; // optional with default value
        private boolean ham = false;      // optional with default value
        private int sausageCount = 0;     // optional with default value

        // We set size in the constructor, enforce the client sets it (as it's a mandatory field)
        public Builder(int size) {
            this.size = size;
        }

        public Builder addOnions() {
            this.onion = true;
            return this;
        }

        public Builder addCheese() {
            this.cheese = true;
            return this;
        }

        public Builder addOlives() {
            this.olives = true;
            return this;
        }

        public Builder addTomato() {
            this.tomato = true;
            return this;
        }

        public Builder addMushrooms() {
            this.mushroom = true;
            return this;
        }

        public Builder addHam() {
            this.ham = true;
            return this;
        }

        public Builder sausageCount(int sausageCount) {
            this.sausageCount = sausageCount;
            return this;
        }

        public Pizza build() {
            return new Pizza(this);
        }
    }
}

All the builder setter methods return the builder itself (notice how they all return this). This allows the setter invocations can be “chained”.

Coming back to the construction of the Pizza, its constructor needs to be private so that it cannot be instantiated directly by “outsider” classes. However, it will of course be instantiated through the inner class Builder’s build() method!

public class Pizza {

    private int size;           // mandatory
    private boolean onion;      // optional
    private boolean cheese;     // optional
    private boolean olives;     // optional
    private boolean tomato;     // optional
    private boolean mushroom;   // optional
    private boolean ham;        // optional
    private int sausageCount;   // optional

    private Pizza(Builder builder) {
        this.size = builder.size;
        this.onion = builder.onion;
        this.cheese = builder.cheese;
        this.olives = builder.olives;
        this.tomato = builder.tomato;
        this.mushroom = builder.mushroom;
        this.ham = builder.ham;
        this.sausageCount = builder.sausageCount;
    }

So, creating an instance of the Pizza using the builder pattern will look like this:

Pizza pizza = new Pizza.Builder(10) // note that this "new" creates a "Pizza.Builder" instance, not a "Pizza"!
                .addCheese()        // add cheese to the builder instance, returns the builder instance,
                .addOlives()        // add olives to the builder instance, returns the builder instance,
                .sausageCount(2)    // add 2 sausages to the builder instance, returns the builder instance,
                .addTomato()        // add tomato to the builder instance, returns the builder instance,
                .build();           // now we create the "Pizza" instance!

Note that, in our case the pizza instance is immutable. If we want to be able change its state after creation, we would have to add public setter methods to the Pizza class.

In the meantime, this is the final result of what we developed above:



Edit Get your hands dirty!

Apply the Builder design pattern to this SoupNoodleclass:

The fields noodleType and size are mandatory, while the fields extraNoodle, extraVeggies, bambooShoots, egg are optional (default values are false).







Prototype (Creational)

Problem: In OOP, we often need to create copies of objects. For example, when implementing a game with an object representing an avatar, we might find it convenient to use this object as a prototype to create thousands of copies and then make minor modifications, such as slightly changing the size. For instance, we may want to create a crowd of people walking on the street. How can this be achieved? How do we create an exact copy of an object? By now, it should be clear that an object is characterized by its field values. So, to make a copy of an object, we can create an object of the same type and set the same field values. This might sound easy but can be impossible in some cases.

For example, let’s consider the following Avatar class:

public class Avatar {
    private int size;
    private int walkingSpeed;
    private int walkingDirection;

    public Avatar(int size, int walkingSpeed, int walkingDirection) {
        this.size = size;
        this.walkingSpeed = walkingSpeed;
        this.walkingDirection = walkingDirection;
    }

    public int getSize() {
        return size;
    }

    @Override
    public String toString() {
        return "Avatar [size=" + size + ", walkingSpeed=" + walkingSpeed + ", walkingDirection=" + walkingDirection
                + "]";
    }

  }


Now, let’s say we have an Avatar object with size 5, walkingSpeed 4, and walkingDirection 5. This object is created at runtime we don’t know these values when writing the code. We could do something like this:

Avatar avatar = new Avatar(getSize(), …. WAIT! walkingSpeed and walkingDirection are private but do not have getters. This is quite common, as not every private field necessarily needs a getter. Some fields are only internal and are not meant to be visible from outside of the object itself. Can we add the getters and do? Avatar avatar = new Avatar(getSize(), getWalingSpeed(), getWalkingDirection()); but what if we want to clone an object implemented not by us but imported from a third-party library? We cannot change their code! What if we don’t know the type of the object, but only the one of its superclass?

Solution: Use the Prototype design pattern, which involves delegating the cloning procedure to the objects being replicated. This pattern establishes a shared interface for all objects capable of cloning, allowing you to duplicate an object without tying our code to the object’s specific class. Typically, this interface includes only one method for cloning.

public interface Prototype {
    Prototype copy();
  }

Now, we just have to implement this interface and write the implementation of the method copy(). This time it’s easy because we are inside the object and have access to all fields.

public class Avatar implements Prototype{
    private int size;
    private int walkingSpeed;
    private int walkingDirection;
    public Avatar(int size, int walkingSpeed, int walkingDirection) {
        this.size = size;
        this.walkingSpeed = walkingSpeed;
        this.walkingDirection = walkingDirection;
    }

    public int getSize() {
        return size;
    }


    @Override
    public String toString() {
        return "Avatar [size=" + size + ", walkingSpeed=" + walkingSpeed + ", walkingDirection=" + walkingDirection
                + "]";
    }

    @Override
    public Prototype copy() {
        return new Avatar(size, walkingSpeed, walkingDirection);
    }

  }

The following code

  Avatar avatar = new Avatar(50, 30, 6);
  System.out.println(avatar);
  Avatar clone = (Avatar) avatar.copy();
  System.out.println(clone);

prints:

Avatar [size=50, walkingSpeed=30, walkingDirection=6]
Avatar [size=50, walkingSpeed=30, walkingDirection=6]

Actually, Java implements this design pattern, the interface Cloneable tells the JDK that is safe to use the clone method of java.lang.Object to clone an object. clone is anlougus to copy above. Let’s mofify the Avatar class to use the Cloneable interfaces instead of our Prototype interface.


public class Avatar implements Cloneable {
  private int size;
  private int walkingSpeed;
  private int walkingDirection;

  public Avatar(int size, int walkingSpeed, int walkingDirection) {
    this.size = size;
    this.walkingSpeed = walkingSpeed;
    this.walkingDirection = walkingDirection;
  }

  public int getSize() {
    return size;
  }

  @Override
  public String toString() {
    return "Avatar [size="
        + size
        + ", walkingSpeed="
        + walkingSpeed
        + ", walkingDirection="
        + walkingDirection
        + "]";
  }

  public static void main(String[] args) throws CloneNotSupportedException {
    Avatar avatar = new Avatar(50, 30, 6);
    System.out.println(avatar);
    Avatar clone = (Avatar) avatar.clone();
    System.out.println(clone);
    Object a
  }
}

We even no need to implement the clone method, as in this case we can use the standard of Java that copies each of the fields. If we want to use a custom type of clone that does not copy all fields, we can override the clone method and give our implementation.



Edit Get your hands dirty!

Apply the Prototype design pattern to these classes:

public class Classroom  {
    private String id;
    private List<Student> students;

    public Classroom(String id) {
        this.id = id;
        this.students = new ArrayList<>();
    }

    public void addStudent(Student student) {
        this.students.add(student);
    }

    @Override
    public String toString() {
        return "Classroom{id='" + id + "', students=" + students.toString() + "}";
    }
}
public class Student  {
    private String name;
    private String upi;

    public Student(String name, String upi) {
      this.name = name;
      this.upi = upi;
    }
    @Override
    public String toString() {
        return "Student{name='" + name + "' upi='" + upi + "'}";
    }
}

then implement the Main class:

public class Main{
    public static void main(String[] args) {
      // create a classroom

      // add some students


      // clone the classroom
    }
}

Does both Classroom and Student have to implement Prototype interface and implement the copy method?

BONUS do the same exercise but use the JDK Cloneable interface. Do you need to implement the copy method(s)?







Adapter (Structural)

Let’s assume that you’re visiting Europe. Your laptop expects a NZ power supply (type I). To get your laptop plugged in, you need to get a power adapter that accepts your NZ plug, and allows it to plug in to the European power outlet. The AC adapter knows how to deal with both sides, acting as a middleperson - this is the adapter pattern.

Problem: We have classes with similar behaviour that have incompatible interfaces.

Solution: Create a class that translates the request from one class to another (Adapter design pattern). In a nutshell, Adapter is a structural design pattern that allows objects with incompatible interfaces to collaborate.

Let’s see an example.

Assume that we have an interface with a sort() method that sorts an array of integers (in reverse order).


public interface Sorter {
     int[] sort(int[] numbers);
}

Assume that we want to use an existing library that provides a sorting functionality with reverse order through its ListSorter class.


import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

// library class that we cannot change
public class ListSorter {
    public List<Integer> sortList(List<Integer> numbers)   {
        //copy the list number into another list
        List<Integer> copyList = new ArrayList<>(numbers);
        Collections.sort(copyList,  Collections.reverseOrder());
        return copyList; // return the sorted list
    }
}

There is a problem! The ListSorter class sorts lists, not arrays! What can we do?

We cannot change the ListSorter class to take input arrays. This is because we are not the developers of the library. If we change the method sortList, we might introduce bugs and also we need to change it every time a new version of the library will be released. Moreover, we might not have access to the library source code, making it impossible to change the method.

The Adapter design pattern tells us to create a class SortListAdapter that implements the interface Sorter. Its sort() method converts int[] to List<Integer>, calls the sortList() method of the class ListSorter, and then converts the sorted list back to an array.

public class SortListAdapter implements Sorter {

    @Override
    public int[] sort(int[] numbers) {
        List<Integer> numberList = convertArray2List(numbers);

        ListSorter sorter = new ListSorter();
        List<Integer> listSorted = sorter.sortList(numberList);
        int[] arraySorted = convertList2Array(listSorted);

        return arraySorted;
    }
}

The conversion is done with two helper methods:

private List<Integer> convertArray2List(int[] numbers) {
    List<Integer> list = new ArrayList<>();
    for (int number : numbers) {
        list.add(number);
    }
    return list;
}

private int[] convertList2Array(List<Integer> numbers) {
    int[] array = new int[numbers.size()];
    for (int i = 0; i < numbers.size(); i++) {
        array[i] = numbers.get(i);
    }
    return array;
}

Now, if we want to sort an array of integers we can do so like this:

public static void main(String[] args) {
    int[] numbers = new int[] { 5, 34, 2, -1, 3, 444, 89 };
    System.out.println("array in input: " + Arrays.toString(numbers));

    Sorter sorter = new SortListAdapter();

    int[] sortedNumbers = sorter.sort(numbers);
    System.out.println("ordered array (reverse order): " + Arrays.toString(sortedNumbers));
}

This is the final result:



Edit Get your hands dirty!

Apply the Adapter design pattern to this program.

Similarly to the sort example, we have an interface Max:

public interface Max{
     int getMax(int[] numbers);
}

that returns the biggest elements of an array.

We have the class ListMax

public class ListMax {

    public int getMaxFromList(List<Integer> numbers) {
        //TODO implement by yourself
        return 0; //fix me
    }
}

You need to complete the classes ListMaxand Main using the Adapter design pattern. In particular, you need to also create a MaxListAdapter class to adapt the two interfaces.






Composite (Structural)

Problem:

Sometimes you have to work on a program that deals with entities that can be organized into a tree-like structure.

For instance, let’s say you’re building a File System. You’ll have two classes: File and Folder. A Folder can contain other folders or files. This is a classic example of a tree-like structure, with files as the leaves and folders as the nodes.

Both folders and files might also share similar properties, like having a name and size.

Check out this example implementation:

class File {
    private String name;
    private int size;

    public File(String name, int size) {
        this.name = name;
        this.size = size;
    }

    public void display() {
        System.out.println("File -> " + name);
    }

    public int getSize() {
        return size;
    }
}

A Folder has a name and a list of files and folders.

class Folder {
    private String name;
    private List<File> files = new ArrayList<>();
    private List<Folder> folders = new ArrayList<>();

    public Folder(String name) {
        this.name = name;
    }

    public void addFile(File file) {
        files.add(file);
    }

    public void removeFile(File file) {
        files.remove(file);
    }

    public void addFolder(Folder folder) {
        folders.add(folder);
    }

    public void removeFolder(Folder folder) {
        folders.remove(folder);
    }

    public void display() {
        System.out.println("Folder -> " + name);
        for (File file : files) {
            file.display();
        }
        for (Folder folder : folders) {
            folder.display();
        }
    }
}

But there’s a problem with this design. What if we want to compute the size of a folder? We’d have to do something like this:

    public int totalSize() {
        int size = 0;
        for (File file : files) {
            size = size + file.getSize();
        }
        for (Folder folder : folders) {
            size = size + folder.totalSize();
        }
        return size;
    }

This isn’t great, because we end up with two similar for loops that do the same thing. The design leads to lots of duplicated code and makes the program hard to maintain and extend. If we want to add new functionality or extend the current functionality, we’ll have to make changes in both File and Folder classes, which is time-consuming and error-prone.

In cases like this, we want to treat objects in the structure the same way.

Solution

The Composite design pattern can help! It lets you work with tree-like structures by treating individual objects and groups of objects (called composites) uniformly (i.e, in the same way).

To implement the Composite pattern, you’ll need to create three things:

Element – a base interface or abstract class with common methods for managing child composites. It should be either an interface or an abstract class.

Leaf – implements the default behavior of the base component.

Composite – implements the base component methods.

Here’s how to implement the Composite pattern in our example:

Element Create a common interface (or abstract class) to treat files and folders uniformly.

interface FileSystemElement {
    void display();
    int  getSize();
}

Leaf The leaf File will be:

class File implements FileSystemElement {
    private String name;
    private int size;

    public File(String name, int size) {
        this.name = name;
        this.size = size;
    }

    @Override
    public void display() {
        System.out.println("File -> " + name);
    }

    @Override
    public int getSize() {
        return size;
    }
}

Next, create the Composite class.

class Folder implements FileSystemElement {
    private String name;
    private List<FileSystemElement> components = new ArrayList<>();

    public Folder(String name) {
        this.name = name;
    }

    public void addComponent(FileSystemElement component) {
        components.add(component);
    }

    public void removeComponent(FileSystemElement component) {
        components.remove(component);
    }

    @Override
    public void display() {
        System.out.println("Folder -> " + name);
        for (FileSystemElement component : components) {
            component.display();
        }
    }

    @Override
    public int getSize() {
        int size = 0;
        for (FileSystemElement component : components) {
            size = size + component.getSize();
        }
        return size;
    }
}

By applying the Composite desing pattern we will have a less redundant, more maintainable, and flexible code. By having a common interface, we can easily add new functionality or extend existing functionality without having to duplicate code or modify multiple classes.



Edit Get your hands dirty!

Apply the Composite design pattern to this program.

import java.util.ArrayList;
import java.util.List;

class Developer {
    private String name;

    public Developer(String name) {
        this.name = name;
    }

    public void displayEmployeeDetails() {
        System.out.println("Developer: " + name);
    }
}

class Manager {
    private String name;

    public Manager(String name) {
        this.name = name;
    }

    public void displayEmployeeDetails() {
        System.out.println("Manager: " + name);
    }
}

class Department {
    private String name;
    private List<Developer> developers = new ArrayList<>();
    private List<Manager> managers = new ArrayList<>();

    public Department(String name) {
        this.name = name;
    }

    public void addDeveloper(Developer developer) {
        developers.add(developer);
    }

    public void removeDeveloper(Developer developer) {
        developers.remove(developer);
    }

    public void addManager(Manager manager) {
        managers.add(manager);
    }

    public void removeManager(Manager manager) {
        managers.remove(manager);
    }

    public void displayEmployeeDetails() {
        System.out.println("Department: " + name);
        for (Developer developer : developers) {
            developer.displayEmployeeDetails();
        }
        for (Manager manager : managers) {
            manager.displayEmployeeDetails();
        }
    }
}






Command (Behavioural)

Problem

Consider the processing of ordering at a restaurant. We have three entities:

  • Customer, who makes an order.
  • Waiter, who communicates the order to the chefs.
  • Chefs, who cook the meal.

A similar situation is common in OO programs where we have Invoker objects that invoke operations on different Receiver objects. Following the restaurant analogy, the Invoker is the Waiter, and the Receivers are the Chefs. If the Invoker and Receiver interact with each other directly, the Invoker will have a series of if statements to decide which receiver to invoke based on a given command (a kitchen might have multiple chefs that cook different meals).

if (command == A){
   // invoke receiver 1
}else if(command == B){
   // invoke receiver 2
} else if(command == C){
    ....

This situation results in a big headache when we need to add new receivers (chefs) and/or command types (meals). You will need to add more if-elses. The complexity and maintainability of the code will increase in case there are lots of receivers and/or command types.

Solution: The Command Design Pattern decouples an object making a request (Invoker), from the one that knows how to perform it (Receiver). It separates the object that invokes the operation from the object that actually performs the operation. It makes it easy to add new commands because existing classes remain unchanged.

Following the analogy of the restaurant, we can apply the Command Design Pattern as follows:

  • The Customer makes an order.
  • The Waiter writes the order on a piece of paper and places it on the Kitchen table and rings a bell.
  • The right Chef will take the order from the table and cook the meal.

All the information on what to cook is in the order itself (the piece of paper), the Chef does not communicate directly with neither the Waiter nor the Costumer. The Waiter might not know how to cook the meal or which Chef is going to cook the meal, the Waiter just delivers the order.

This is the Command Design Pattern!

The components of this design pattern are:

  • Command declares an interface for abstract commands (e.g., execute()).

  • ConcreteCommand are classes that implement the Command interface. Internally, they have the reference of who is the receiver of the implemented command.

  • Receiver knows how to execute a particular command.

  • Invoker holds the ConcreteCommand that has to be executed.

  • Client creates a ConcreteCommand and give it to the Invoker.

In this way, the Command Design Pattern decouples the Invoker from Receiver. The Invoker has complete knowledge of which ConcreteCommand to be executed and the ConcreteCommand knows which Receiver to be invoked to execute a particular operation.

Let’s see a concrete example. We want to implement a remote controller for a home automation system that controls different electrical units of a home. For simplicity, let’s assume we have two electrical units: a Light and a GarageDoor. Both are the Receivers using the terminology of this design pattern.

// Receiver
public class Light {
	private String location;

	public Light(String location) {
		this.location = location;
	}

	public void on() {
		System.out.println(location + " light is on");
	}

	public void off() {
		System.out.println(location + " light is off");
	}
}

// Receiver
public class GarageDoor {

	public void up() {
		System.out.println("Garage Door is Up");
	}

	public void down() {
		System.out.println("Garage Door is Down");
	}

	public void lightOn() {
		System.out.println("Garage light is on");
	}

	public void lightOff() {
		System.out.println("Garage light is off");
	}
}

Now let’s create the Command interface, which is very simple, it just declares the method execute.

// Command
public interface Command {
	void execute();
}

Now let’s create 4 concrete commands implementations:

//Concrete Command
public class LightOnCommand implements Command {

	private Light light;

	public LightOnCommand(Light light) {
		this.light = light;
	}

	@Override
	public void execute() {
		light.on();
	}
}
//Concrete Command
public class LightOffCommand implements Command {

	private Light light;

	public LightOffCommand(Light light) {
		this.light = light;
	}

	@Override
	public void execute() {
		light.off();
	}
}
//Concrete Command
public class GarageDoorUpCommand implements Command {
	private GarageDoor door;

	public GarageDoorUpCommand(GarageDoor door) {
		this.door = door;
	}

	@Override
	public void execute() {
		door.up();
		door.lightOn();
	}

}
//Concrete Command
public class GarageDoorDownCommand implements Command {
	private GarageDoor door;

	public GarageDoorDownCommand(GarageDoor door) {
		this.door = door;
	}

    @Override
	public void execute() {
		door.down();
		door.lightOff();
	}

}

Now we can implement the RemoteController that is our Invoker.

//Invoker
public class RemoteController{

  private Command command;

  public void setCommand(Command command){
    this.command = command;
  }

  public void pressButton(){
    command.execute();
  }

}

This is an example of Client that use the classes that we have just created.

//Client
public class Main {

  public static void main(String[] args) {
    RemoteController invoker = new RemoteController();
    Light lightBedroom = new Light("Bedroom");
    Command lightsOn = new LightOnCommand(lightBedroom);
    Command lightsOff = new LightOffCommand(lightBedroom);
    Command garageUp = new GarageDoorUpCommand(new GarageDoor());
    Command garageDown = new GarageDoorDownCommand(new GarageDoor());

    // light switch on
    invoker.setCommand(lightsOn);
    invoker.pressButton();
    // light switch off
    invoker.setCommand(lightsOff);
    invoker.pressButton();
    // garage up
    invoker.setCommand(garageUp);
    invoker.pressButton();
    // garage down
    invoker.setCommand(garageDown);
    invoker.pressButton();
	}

}

Invoker with history

Now with the Command Design Pattern implemented we can do cool things like adding a queue and a history of executed commands. We just need to change the implementation of the Invoker.

import java.util.ArrayList;
import java.util.List;

//Invoker
public class RemoteController {

  private List<Command> historyCommands;
  private List<Command> queueCommands;

  public RemoteController() {
    historyCommands = new ArrayList<>();
    queueCommands = new ArrayList<>();
  }

  public List<Command> getHistoryCommands() {
    return historyCommands;
  }

  public List<Command> getQueueCommands() {
    return queueCommands;
  }

  public void addCommand(Command command) {
    queueCommands.add(command);
  }

  public void executeAll() {
    for (Command command : queueCommands) {
      command.execute();
      historyCommands.add(command);
    }
    queueCommands.clear();
  }
}

Now the Invoker stores all executed commands and queue the ones to be executed.

Command design pattern with undo

This design pattern is also useful when we want to implement an “undo” of a command. In such a case, we need to add the unexecute operation in the Command interface and implement it in all of the ConcreteCommands. Intuitively, the “unexecute” operation has to mirror the execute operation. For example,


public interface Command {
	public abstract void execute();
	public abstract void unexecute();
}


public class GarageDoorUpCommand implements Command {
	private GarageDoor door;

	public GarageDoorUpCommand(GarageDoor door) {
		this.door = door;
	}

	@Override
	public void execute() {
		door.up();
		door.lightOn();
	}

	@Override
	public void unexecute() {
		door.down();
		door.lightOff();

	}

}



Edit Get your hands dirty!

The REPL below has an additional Receiver, the following Fan class:


public class Fan {
	static final int HIGH = 3;
	static final int MEDIUM = 2;
	static final int LOW = 1;
	static final int OFF = 0;
	private int speed;

	public Fan() {
		speed = OFF;
	}

	public void high() {
		speed = HIGH;
		System.out.println("Fan is on high");
	}

	public void medium() {
		speed = MEDIUM;
		System.out.println("Fan is on medium");
	}

	public void low() {
		speed = LOW;
		System.out.println("Fan is on low");
	}

	public void off() {
		speed = OFF;
		System.out.println("Fan is off");
	}

	public int getSpeed() {
		return speed;
	}
}

This Receiver represents a Fan with three speeds, high, medium, and low. Implement three concrete commands: FanHighCommand, FanMediumCommand, and FanLowCommandthat set the speed high, medium, and low, respectively. You need to also implement the unexecute operations. This means that you need to save the state of the current speed to be restored in case the client executes unexecute.

</div>





Strategy (Behavioural)

Let’s assume that we need to implement the payment system of a shop. Currently, the shop handles three different types of payment: by cash, by Visa card, or by MasterCard. If cards are used, a fee is to be applied.

The most obvious (but not very good) implementation could be something like this:

public class Order {

    private final String payment;
    private final int amount;

    private double feeMastercard = 0.0015;
    private double feeVisaCard = 0.001;

    public Order(int amount, String payment) {
        this.amount = amount;
        this.payment = payment;
    }

    public void payCash(int amount) {
        System.out.println("Executing cash payment: Charging $" + amount);
    }

    public void payMastercard(int amount) {
        System.out.println("Executing Mastercard payment: Charging $" + amount);
        System.out.println("fees " + feeMastercard * amount);
    }

    public void payVisaCard(int amount) {
        System.out.println("Executing Visacard payment: Charging $" + amount);
        System.out.println("fees " + feeVisaCard*amount);
    }

    public void process(){
        switch (payment){
            case "visa":
                payVisaCard(amount);
                break;
            case "master":
                payMastercard(amount);
                break;
            case "cash":
                payCash(amount);
                break;
            default:
                System.out.println("unknonw payment");
        }
    }

    public static void main(String[] args) {
        Order order1 = new Order(15, "visa");
        order1.process();

        Order order2 = new Order(100, "master");
        order2.process();

        Order order3 = new Order(100, "cash");
        order3.process();
    }

}

There are several “problems” with this solution:

  • We need to update the process() method every time we add or delete a payment type.
  • We can easily pass a wrong payment String (for example, “visacard” instead of “visa”)

This scenario is the perfect case to apply the Strategy Pattern.

We can isolate each of the payment in different classes, called strategies, that define an interface common to all supported payments. Then the Order class uses this interface to execute the algorithm implemented by a Concrete Strategy. In this way, ordering a payment is independent from the strategy. We can add a new algorithm or update the existing one without changing the Order class. With this approach, our payment program becomes much more flexible.

Problem: We have a class that switches between algorithms to accomplish the same task. This means that we would like the option to change the algorithms at runtime.

Solution: The Strategy pattern takes a class that does something specific in a lot of different ways and extract all of these algorithms into separate classes called strategies. The Strategy pattern helps to define a family of algorithms, to encapsulate each one of them and make them interchangeable and independent from the classes that use them. Most importantly, it allows changing the strategy at runtime.

Let’s see how to apply this design pattern in our example.

We need an interface for our strategy (the payment):

public interface Payment {
    void pay(int amount);
}

This interface has a single method pay().

Let’s create our “family” of the three payment algorithms:

public class CashPayment implements Payment {
    @Override
    public void pay(int amount) {
        System.out.println("Executing cash payment: Charging $" + amount);
    }
}

public class MastercardPayment implements Payment {
    double fee = 0.0015;

    @Override
    public void pay(int amount) {
        System.out.println("Executing Mastercard payment: Charging $" + amount);
        System.out.println("fees " + fee * amount);
    }
}

public class VisacardPayment implements Payment {
    double fee = 0.001;

    @Override
    public void pay(int amount) {
        System.out.println("Executing Visacard payment: Charging $" + amount);
        System.out.println("fees " + fee * amount);
    }
}

Note that the three algorithms can implement very different behaviours. The important thing is, that they must all implement the same Payment interface.

Now we need to modify the Order class that makes use of the strategies:

public class Order {

    private final Payment payment;
    private final int amount;

    public Order(int amount, Payment payment) {
        this.amount = amount;
        this.payment = payment;
    }

    public void process() {
        payment.pay(amount);
    }
}

Note that now the process() method is no longer responsible for selecting an appropriate payment. Instead, we pass the desired strategy as an object (Payment payment).

The class Order will make the payment through the interface Payment. The Order class does not need to know the concrete payment processing. That means that in the future, we can add more strategies without changing the Order class at all!

Now we can use the Order class as follows:

Order order1 = new Order(15, new VisacardPayment());
order1.process();

Order order2 = new Order(100, new MastercardPayment());
order2.process();

Order order3 = new Order(100, new CashPayment());
order3.process();

One of the greatest advantage of this design pattern is that it allows to easily change algorithms (strategies) at runtime. We just need to add the setter method of the strategy instance:

public class Order {

    private Payment payment;
    private final int amount;

    public Order(int amount, Payment payment) {
        this.amount = amount;
        this.payment = payment;
    }

    public void setPayment(Payment payment){
        this.payment = payment;
    }

    public void process() {
        payment.pay(amount);
    }
}

Now at runtime we can change the payment strategy! How cool is that!?

Putting everything together:





Edit Get your hands dirty!

Consider the example above. Add the feature that credit card payments can be declined, if a card payment is declined the payment strategy should be switch to cash payment.



REWIND!

Download the ACP exercise 281_Rewind_Rock_Paper_Scissor

Let’s implement some Artificial Intelligence (AI) to play rock-paper-scissors against a human player!

  • Run the Main class and get familiar with the game. Currently the CPU and Humans always answer the same thing:
public class CPU {

	public Action play() {
		return Action.ROCK;
	}

}
  • implement the logic for the Human (human will interact with the console)

Let’s use the Strategy design pattern and create three strategies for the AI, and then we will change them at runtime.

  • Random The first strategy is random, it randomly choose an Action (Rock, Paper, or Scissors).
  • Cycle The second strategy cycles between Rock, Paper, and Scissors. First game Rock, second game Paper, third game Scissors, fourth game Rock, fifth game Paper and so on.
  • Last The third strategies plays the hand that the opponent (Human player) just played. At the first round it plays PAPER.

Now at runtime we want to change these strategies. We start with the Cycle strategy, when the CPU losses 3 times in a row the game changes the strategy to Last, because the player guessed the CPU strategy!. Then, if the CPU losses other 3 times in a row, it likely means that the Human player guessed also this strategy and the game changes it to Random. The game keeps the Random strategy until the player decides to stop the game.

BONUS When the user ends the game print the game statistics (number of games won by the human player and the CPU )

SUPER BONUS When the user ends the game print also which strategy performed the best (in percentage of round played)