Design patterns - PawelBogdan/BecomeJavaHero GitHub Wiki

Table of contents

  1. Introduction
  2. Singleton
  3. Abstract factory
  4. Factory method pattern
  5. Builder
  6. Prototype
  7. Adapter
  8. Bridge pattern
  9. Composite
  10. Decorator pattern
  11. Facade pattern
  12. Proxy Pattern
  13. Chain of responsibility
  14. Null object pattern
  15. Observer pattern
  16. State pattern
  17. Strategy pattern
  18. Template method pattern
  19. Exercises
  20. Sources

Introduction

According to wikipedia

software design pattern is a general reusable solution to a commonly occurring problem within a given context in software design. It is not a finished design that can be transformed directly into source or machine code. It is a description or template for how to solve a problem that can be used in many different situations. Design patterns are formalized best practices that the programmer can use to solve common problems when designing an application or system.
Object-oriented design patterns typically show relationships and interactions between classes or objects, without specifying the final application classes or objects that are involved.

We can divide patterns into following categories (according to wikipedia pages):

  1. Creational patterns, for example (quote from here):

    1. Abstract factory pattern, which provides an interface for creating related or dependent objects without specifying the objects' concrete classes.
    2. Builder pattern, which separates the construction of a complex object from its representation so that the same construction process can create different representations.
    3. Factory method pattern, which allows a class to defer instantiation to subclasses.
    4. Prototype pattern, which specifies the kind of object to create using a prototypical instance, and creates new objects by cloning this prototype.
    5. Singleton pattern, which ensures that a class only has one instance, and provides a global point of access to it.
  2. Structural patterns, for example (quote from here)

    1. Adapter pattern: 'adapts' one interface for a class into one that a client expects
      • Adapter pipeline: Use multiple adapters for debugging purposes.
      • Retrofit Interface Pattern: An adapter used as a new interface for multiple classes at the same time.
    2. Aggregate pattern: a version of the Composite pattern with methods for aggregation of children
    3. Bridge pattern: decouple an abstraction from its implementation so that the two can vary independently Tombstone: An intermediate "lookup" object contains the real location of an object.
    4. Composite pattern: a tree structure of objects where every object has the same interface
    5. Decorator pattern: add additional functionality to a class at runtime where subclassing would result in an exponential rise of new classes
    6. Extensibility pattern: a.k.a. Framework - hide complex code behind a simple interface
    7. Facade pattern: create a simplified interface of an existing interface to ease usage for common tasks
    8. Flyweight pattern: a large quantity of objects share a common properties object to save space
    9. Marker pattern: an empty interface to associate metadata with a class.
    10. Pipes and filters: a chain of processes where the output of each process is the input of the next
    11. Opaque pointer: a pointer to an undeclared or private type, to hide implementation details
    12. Proxy pattern: a class functioning as an interface to another thing
  3. Behavioural patterns, for example (quote from here)

    1. Chain of responsibility pattern: Command objects are handled or passed on to other objects by logic-containing processing objects
    2. Command pattern: Command objects encapsulate an action and its parameters
    3. "Externalize the Stack": Turn a recursive function into an iterative one that uses a stack
    4. Interpreter pattern: Implement a specialized computer language to rapidly solve a specific set of problems
    5. Iterator pattern: Iterators are used to access the elements of an aggregate object sequentially without exposing its underlying representation
    6. Mediator pattern: Provides a unified interface to a set of interfaces in a subsystem
    7. Memento pattern: Provides the ability to restore an object to its previous state (rollback)
    8. Null Object pattern: Designed to act as a default value of an object
    9. Observer pattern: a.k.a. Publish/Subscribe or Event Listener. Objects register to observe an event that may be raised by another object
      • Weak reference pattern: De-couple an observer from an observable
    10. Protocol stack: Communications are handled by multiple layers, which form an encapsulation hierarchy
    11. Scheduled-task pattern: A task is scheduled to be performed at a particular interval or clock time (used in real-time computing)
    12. Single-serving visitor pattern: Optimise the implementation of a visitor that is allocated, used only once, and then deleted
    13. Specification pattern: Recombinable business logic in a boolean fashion
    14. State pattern: A clean way for an object to partially change its type at runtime
    15. Strategy pattern: Algorithms can be selected on the fly
    16. Template method pattern: Describes the program skeleton of a program
    17. Visitor pattern: A way to separate an algorithm from an object

Singleton

We have already talked about this pattern. We use it, when we need to have only one instance of some class in the whole application. Let's consider how to make the Singleton thread-safe. See class pl.edu.bogdan.training.patterns.Singleton and makie it thread-safe.

Abstract factory

Consider a situation, we have family of similar object, which are created in different ways. Wikipedia uses example where we have an abstract factory for letter creation. There can be many implementations of the factory, OficialLetterFactory, FancyLetterFactory and so on. Every class implements factory method which returns specific Letter object. It divides implementation from interfaces. Look at example:

public interface Figure {
	public double getArea();
	public double getPerimeter();
}

public interface FigureFactory {
	Figure createFigure();
}

public class CircleFactory implements FigureFactory {
	@Override
	public Figure createFigure() {
		return new Circle();
	}
}

Final user will use object Circle but know nothing about its inside implementation.

Factory method pattern

Let's consider the situation of Computer class:

public abstract class Computer {
	public IProcessor processor;
	
	public Computer() {
		processor = createProcessor();
	}
	
	abstract protected IProcessor createProcessor();
}

And we can create different computers by extending this class.

Builder

We can use this pattern to convenient creation of objects which have many parameters. See a simple example below:

public class Circle {
	private double x;
	private double y;
	private double radius;
	
	private Circle(Builder builder) {
		x = builder.x;
		y = builder.y;
		radius = builder.radius;
	}
	
	public static class Builder {
		private double x;
		private double y;
		private double radius;
		
		public Builder() {
			
		}
		
		public Builder x(double x) {
			this.x = x;
			return this;
		}
		
		public Builder y(double y) {
			this.y = y;
			return this;
		}
		
		public Builder radius(double radius) {
			this.radius = radius;
			return this;
		}
		
		public Circle build() {
			return new Circle(this);
		}
	}
}

How do you use this pattern? Consider class pl.edu.bogdan.training.patterns.builder.exercise.NutritionFacts and make a builder for it.

Prototype

We use this pattern, when we need to create very similar objects and defines only differences. For example, we want to draw picture consists of circles, every circle has the same colour, the same border and other properties. Circles differs by coordinates of centre.

It is very useful to use Clonable interface. Implement an example.

Adapter

This pattern is used in situation when we have an object of one interface but, we want to use it as it would be an object of other interface. Let's consider XML files. There are two methods of keeping a XML file in computer memory: DOM and SAX. We have an object implementing interface for SAX mechanism, but we want to use this object as implementation of DOM mechanism. This is scenario when we use an adapter pattern. See for example:

public interface IDOM {

}

public interface ISAX {

}

public class Adapter implements IDOM {
	private final ISAX sax;
	
	public Adapter(final ISAX sax) {
		this.sax = sax;
	}
	
	// methods of IDOM implemented using sax field
}

Bridge pattern

This pattern is used to hide implementation of some abstraction. Consider example of class Shape and this class uses some abstraction to display. Look at source code below

public interface Renderer {
	void render(Object o);
}

public abstract class Shape {
	protected Renderer renderer;

	public Shape(Renderer renderer) {
		super();
		this.renderer = renderer;
	}
	
	public abstract void draw();
}

public class CircleRenderer implements Renderer {
	@Override
	public void render(Object o) {
		Circle circle = (Circle) o;
		System.out.println("I am drawing circle");
	}
}

public class Circle extends Shape {
	public Circle(Renderer renderer) {
		super(renderer);
	}

	@Override
	public void draw() {
		renderer.render(this);
	}
}

public class CircleRenderer implements Renderer {
	@Override
	public void render(Object o) {
		Circle circle = (Circle) o;
		System.out.println("I am drawing circle");
	}
}

Composite

We use this pattern in situation when we want to see many objects of one class as one object of this class. As a result we obtain a tree structure. Look at the following example:

public interface Printable {
	public void print();
}

public class Word implements Printable {
	private final String message;
	
	public Word(String m) {
		this.message = m;
	}
	
	@Override
	public void print() {
		System.out.print(message);
	}
}

public class Composite implements Printable {
	private List<Printable> printables;
	
	public Composite() {
		printables = new LinkedList<>();
	}
	
	@Override
	public void print() {
		for (Printable p : printables) {
			p.print();
		}
	}
	
	public void add(Printable p) {
		printables.add(p);
	}
	
	public void remove(Printable p) {
		printables.remove(p);
	}
}

public class CompositeExample {
	public static void main(String[] args) {
		Word w1 = new Word("a");
		Word w2 = new Word("b");
		Word w3 = new Word("c");
		Word w4 = new Word("d");
		Word w5 = new Word("e");
		
		Composite c1 = new Composite();
		Composite c2 = new Composite();
		Composite c3 = new Composite();
		
		c1.add(w1);
		c1.add(w2);
		c1.add(w3);
		
		c2.add(w4);
		c2.add(w5);
		
		c3.add(c1);
		c3.add(c2);
		
		c3.print();
	}
}

Decorator pattern

According to wikipedia:

decorator pattern is a design pattern that allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class.[1] The decorator pattern is often useful for adhering to the Single Responsibility Principle, as it allows functionality to be divided between classes with unique areas of concern.

Let's consider an example. We have a Mail object and we want to expand its functionality in time of running program. For example, we have object of OrdinaryMail and we want to create object of ExpressMail which is based on the object of OrdinaryMail. To do so, we create decorator class. Look at the following example:

public interface Mail {
	public double getPrice();
	public int deliveryTime();
}

public class OrdinaryMail implements Mail {
	@Override
	public double getPrice() {
		return 2.0;
	}

	@Override
	public int deliveryTime() {
		return 7;
	}
}

public abstract class MailDecorator implements Mail {
	private final Mail mail;
	
	public MailDecorator(Mail mail) {
		this.mail = mail;
	}
	
	@Override
	public double getPrice() {
		return mail.getPrice();
	}
	
	public int deliveryTime() {
		return mail.deliveryTime();
	}
}

public class ExpressMail extends MailDecorator {
	public ExpressMail(Mail mail) {
		super(mail);
	}
	
	@Override
	public double getPrice() {
		return super.getPrice() * 3;
	}
	
	@Override
	public int deliveryTime() {
		return super.deliveryTime() / 3;
	}
}

Facade pattern

We use this pattern when we have many objects and we want to simplify access to these objects via one object so called Facade. For example, we have objects representing parts of car: engine, lights, breaks, transmission. Every time we want to drive, we need to start the engine, turn on lights, put the first gear and stop breaking. Using these object separately is inconvenient, so we create car object, which has parts.

public interface Engine {
	public void start();
	public void stop();
}

public interface Lights {
	public void turnOn();
	public void turnOff();
}

public interface Transmission {
	public void putTheGear(int gear);
}

public interface Breaks {
	public void push();
	public void release();
}

public class Car {
	private Engine engine;
	private Breaks breaks;
	private Lights lights;
	private Transmission transmission;
	
	public Car(Engine engine, Breaks breaks, Lights lights, Transmission transmission) {
		super();
		this.engine = engine;
		this.breaks = breaks;
		this.lights = lights;
		this.transmission = transmission;
	}
	
	public void startDriving() {
		breaks.push();
		engine.start();
		lights.turnOn();
		transmission.putTheGear(1);
		breaks.release();
	}
}

Proxy Pattern

In this pattern we create an object to use some another object because there are some restrictions in usage that other object or we want to save memory or time.

Let's consider an example of file. We have an object which stores the content of file and everybody is permitted to use this object. In practice, we do not want to load the content during creation the object, and we want to limit access to this file. The following code implements this example:

public interface File {
	public void displayContent();
}

public class RealFile implements File {
	private String filename;
	
	public RealFile(String filename) {
		this.filename = filename;
		loadContent();
	}
	
	public void loadContent() {
		System.out.println("Reading content");
	}
	
	public void displayContent() {
		System.out.println("This is content");
	}
}

public class User {
	private String name;
	private String role;
	
	public User(String name, String role) {
		this.name = name;
		this.role = role;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getRole() {
		return role;
	}

	public void setRole(String role) {
		this.role = role;
	}
}

public class FileProxy implements File {
	private String filename;
	private User user;
	private RealFile realFile;
	public FileProxy(String filename, User user) {
		super();
		this.filename = filename;
		this.user = user;
	}
	@Override
	public void displayContent() {
		if (user.getRole().equals("root")) {
			if (realFile == null) {
				realFile = new RealFile(filename);
			}
			realFile.displayContent();
		} else {
			System.out.println("You do not permitted to read this file");
		}
	}
}

Chain of responsibility

According to wikipedia

the chain-of-responsibility pattern is a design pattern consisting of a source of command objects and a series of processing objects. Each processing object contains logic that defines the types of command objects that it can handle; the rest are passed to the next processing object in the chain. A mechanism also exists for adding new processing objects to the end of this chain.

Let's consider the school subject and chain of students which can pass the final exam. For example, if first student is too young to pass the exam the subject is passed to the older student and so on. See an example

public class Subject {
	private final String name;
	private final int difficultyLevel;
	public Subject(String name, int difficultyLevel) {
		super();
		this.name = name;
		this.difficultyLevel = difficultyLevel;
	}
	public String getName() {
		return name;
	}
	public int getDifficultyLevel() {
		return difficultyLevel;
	}
}

public class Student {
	private final String firstName;
	private final String lastName;
	private final int level;
	private Student next;
	public Student(String firstName, String lastName, int level) {
		this.firstName = firstName;
		this.lastName = lastName;
		this.level = level;
	}
	public String getFirstName() {
		return firstName;
	}
	public String getLastName() {
		return lastName;
	}
	public int getLevel() {
		return level;
	}
	public void setNext(Student student) {
		next = student;
	}
	public void pass(Subject subject) {
		if (subject.getDifficultyLevel() <= level) {
			System.out.println(subject.getName() + " is passed by " + this);
		} else {
			next.pass(subject);
		}
	}
	@Override
	public String toString() {
		return "Student [firstName=" + firstName + ", lastName=" + lastName + ", level=" + level + "]";
	}
}

public class Chain {
	public static void main(String [] args) {
		Subject spelling = new Subject("Spelling", 0);
		Subject literature = new Subject("Literature", 2);
		Subject quantumPhisics = new Subject("Quantum phisics", 10);
		
		Student johnny = new Student("John", "Smith", 1);
		Student kenny = new Student("Ken", "Brown", 4);
		Student albert = new Student("Albert", "Einstein", 100);
		
		kenny.setNext(albert);
		johnny.setNext(kenny);
		
		johnny.pass(spelling);
		johnny.pass(literature);
		johnny.pass(quantumPhisics);
	}
}

Null object pattern

According to wikipedia

Null Object is an object with no referenced value or with defined neutral ("null") behavior. The Null Object design pattern describes the uses of such objects and their behavior (or lack thereof)

Let's consider the previous example. If the last student in the responsibility chain has too low level for some example NullPointerException is thrown. We can avoid this situation using null object pattern. We can define:

public class NullStudent extends Student {

	public NullStudent(String firstName, String lastName, int level) {
		super(firstName, lastName, level);
	}
	public void pass(Subject subject) {
		System.out.println("There is no student to pass " + subject.getName());
	}
}

Observer pattern

According to wikipedia:

The observer pattern is a software design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods.

We can use Java classes: java.util.Observable and java.util.Observer to implement a thread which will produce random numbers and define observers which will react on these numbers:

public class EventSource extends Observable implements Runnable {
	private Random random = new Random(System.currentTimeMillis());
	@Override
	public void run() {
		while (true) {
			try {
				Thread.sleep(random.nextInt(20));
				int a = random.nextInt(1000);
				setChanged();
				notifyObservers(a);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

public class Test {
	public static void main(String [] args) {
		EventSource eventSource = new EventSource();
		
		eventSource.addObserver(new Observer() {
			@Override
			public void update(Observable o, Object arg) {
				if ((int)arg % 2 == 0) {
					System.out.println("First observer obtained even number: " + arg);
				}
			}
		});
		
		eventSource.addObserver(new Observer() {
			@Override
			public void update(Observable o, Object arg) {
				if ((int)arg % 2 == 1) {
					System.out.println("Second observer obtained odd number: " + arg);
				}
			}
		});
		new Thread(eventSource).start();
	}
}

State pattern

In this pattern object can change its state and its behaviour depends on the state.

Invent and implement an example

Strategy pattern

We can define object of calculator which has a strategy which defines what operation should be perform. Try to implement this example. You can find C# implementation here.

Template method pattern

According to wikipedia:

the template method pattern is a behavioral design pattern that defines the program skeleton of an algorithm in an operation, deferring some steps to subclasses. It lets one redefine certain steps of an algorithm without changing the algorithm's structure.

As an example consider class which sorts objects, and the way of comparing objects is left for subclasses. Try to implement bubble sort.

Exercises

  1. Hakerrank - Factory
  2. Hakerrank - Singleton
  3. Hakerrank - Visitor

Sources

  1. Joshua Bloch Java. Efektywne programowanie, wydanie II
  2. Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides: Inżynieria oprogramowania: Wzorce projektowe (Wyd. II). Warszawa: WNT, 2008.
  3. Wikipedia - Design pattern
  4. Wikipedia - Creational pattern
  5. Wikipedia - Structural pattern
  6. Wikipedia - Behavioural pattern
⚠️ **GitHub.com Fallback** ⚠️