Design Patterns - handbook

Luigi | Nov 5, 2024 min read

I made this project as an easy way to access and revise design patterns.

There are plenty of resources online about design patterns, so why did i made mine?

This is because the resources i found:

  • Do not have the right balance between too verbose and straight to the point explanation.
  • Are not easily searchable: why so many pages-subpages where i can’t just ctrl+f (or cmd+f), and also always so many clicks away to knowing the solution.

So here you go, take a look into this fabulous handbook of design patterns!

Creational

factory method

problem description
Suppose you have a class MyType with 2 implementation MyImpl and MyOtherImpl and suppose this class is used by your application class, like this:

class MyType { }

class MyImpl extends MyType { }

class MyOtherImpl extends MyType { }

class App {
	private MyType mt;
	
	public void init () {
		mt = new MyImpl();
	}
	
	public void destroy () {
		mt = new MyImpl();
	}
}

Now if you change what object is instantated in one of the 2 instantiation, for example in the destroy method, but you forget to change it in the other method, the code will work, but it won’t be correct.

class App {
	private MyType mt;
	
	public void init () {
		mt = new MyImpl();
	}
	
	public void destroy () {
		mt = new MyOtherImpl(); // I change it here but i forget to change it in init
	}
}

It can also happen that you extends the App class, you override only one of the 2 method with another implementation and then you forgot to override the other.

solution using factory method pattern

Factory method pattern propose a solution where you refactor your code to instantiate the object only in one part of your code, so the error could not happen.

class App {
	private MyType mt;
	
	public MyType createMyType() {
		return new MyImpl();
	}
	
	public void init () {
		mt = createMyType();
	}
	
	public void destroy () {
		mt = createMyType(); // I change it here but i forget to change it in init
	}
}
static factory method

problem description
Suppose you have a complex object to build, you can do that in many ways.

solution 1. (bad) use constructor overload
class TimeSpan {

	private int seconds;
	private int minutes;
	private int hours;
	
	private TimeSpan (int seconds, int minutes, int hours) {
		this.seconds = seconds;
		this.minutes = minutes;
		this.hours = hours;
	}
}

In the example above, you will instantiate the object TipeStamp with new TipeStamp(0,10,0). But if you only read this without looking at the implementation of TipeStamp you can’t understand how the object is instantiated.

solution 2. using static factory method pattern

Using static factory method pattern, instead of typing new MyType() you will write MyType.create().

class TimeSpan {

	private int seconds;
	private int minutes;
	
	private TimeSpan (int seconds, int minutes) {
		this.seconds = seconds;
		this.minutes = minutes;
	}
	
	public static TimeSpan ofSeconds (int seconds) {
		return new TimeSpan(seconds, 0);
	}
	
	public static TimeSpan ofMinutes (int minutes) {
		return new TimeSpan(0, minutes);
	}
}

So if you want to add another field, say hours, clients won’t break because they will still call the static method correctly, you are the one that has to change the TimeSpan implementation.

class TimeSpan {

	private int seconds;
	private int minutes;
	private int hours;
	
	private TimeSpan (int seconds, int minutes, int hours) {
		this.seconds = seconds;
		this.minutes = minutes;
		this.hours = hours;
	}
	
	public static TimeSpan ofSeconds (int seconds) {
		return new TimeSpan(seconds, 0, 0);
	}
	
	public static TimeSpan ofMinutes (int minutes) {
		return new TimeSpan(0, minutes, 0);
	}
	
	public static TimeSpan ofHours (int hours) {
		return new TimeSpan(0, 0, hours);
	}
	
}

Notice that:

  1. The constructor is kept private.
abstract factory

problem description
Provide an interface to create family of objects, without specifying directly the class.

solution using abstract factory pattern
VehicleFactory carFactory = new CarFactory();
VehicleFactory bikeFactory = new BikeFactory();

Vehicle myCar = carFactory.createVehicle();
Vehicle myBike = bikeFactory.createVehicle();

myCar.getType();   // "Car"
myBike.getType();  // "Bike"
interface Vehicle {
    String getType();
}

class Car implements Vehicle {
    @Override
    public String getType() {
        return "Car";
    }
}

class Bike implements Vehicle {
    @Override
    public String getType() {
        return "Bike";
    }
}

abstract class VehicleFactory {
    abstract Vehicle createVehicle();
}

class CarFactory extends VehicleFactory {
    @Override
    Vehicle createVehicle() {
        return new Car();
    }
}

class BikeFactory extends VehicleFactory {
    @Override
    Vehicle createVehicle() {
        return new Bike();
    }
}
notes

example class diagram

textbook class diagram

builder

problem description
Suppose you have a complex object to build, you can do that in many ways.

solution 1. overloading the constructor
class Car {
	private String engine; // This is not optional
    private String transmission; // This is optional
    private int airbags; // This is optional
	
	Car (String engine) { ... }
	Car (String engine, String transmission) { ... }
	Car (String engine, String transmission, int airbags) { ... }
}

You can see that as the complexity of the object grows, the constructor gets crammed with more and more parameters, which is not optimal.

solution 2. using setters
Car car = new Car("V8");
car.setTransmission("automatic");
car.setAirbags(5);

Again this is not optimal with complex objects.

solution 3. using builder pattern
The builder pattern propose a solution to this problem, where the construction of the object is handled by (usually) a private static class inside the object itself. So to build the Car object you will do something like this

Car car = new Car.CarBuilder("V6")
	.withTransmission("Automatic")
	.withAirbags(4)
	.build();

System.out.println(car);
// Prints: Car [engine=V6, transmission=Automatic, airbags=4]

The code to do so is the following:

class Car {
    private String engine; // This is not optional
    private String transmission; // This is optional
    private int airbags; // This is optional

    // Private constructor only accessible by builder
    private Car () { }

    public static class CarBuilder {
        private String engine;
        private String transmission = "Manual"; // Default value
        private int airbags = 2;

        public CarBuilder (String engine) {
            this.engine = engine; // This way we are enforcing the car to have at least a motor (the non optional field)
        }
        public CarBuilder withTransmission (String transmission) {
            this.transmission = transmission;
            return this;
        }

        public CarBuilder withAirbags (int airbags) {
            this.airbags = airbags;
            return this;
        }

        public Car build () {
            Car car = new Car(); // It can access private member
            car.engine = engine;
            car.transmission = transmission;
            car.airbags = airbags;
            return car;
        }
    }
    
    @Override
    public String toString () {
        return "Car [engine=" + engine + ", transmission=" + transmission + ", airbags=" + airbags + "]";
    }
}

Notice how:

  • The constructor of the Car class is kept private. Only the Builder class can call it.
  • The non optional parameters of the constructed class has to be passed to the builder class.
  • The builder class has the same exact variables as the Car class.
fluent interface

problem description
We want a readable way to interact with an API.

solution using fluent interface pattern
class Car {
    private String engine;
    private String transmission;
    private int airbags;

    public Car (String engine) { 
        this.engine = engine;
    }
    
    public Car transmission (String transmission) {
        this.transmission = transmission;
        return this;
    }
    
    public Car airbags (int airbags) {
        this.airbags = airbags;
        return this;
    }

    
    @Override
    public String toString () {
        return "Car [engine=" + engine + ", transmission=" + transmission + ", airbags=" + airbags + "]";
    }
}

public class Client {
    public static void main (String[] args) {
        Car car = new Car("V6")
                        .transmission("Automatic")
                        .airbags(4);

        System.out.println(car);
		// Prints: Car [engine=V6, transmission=Automatic, airbags=4]
    }
}
singleton

problem description
We want to

  1. Ensure a class has a single instance
  2. Provide a global access point to that instance
using singleton pattern
public class App {
	private static App instance = null;
	
	private App () { }
	
	public static App getInstance () {
		if (instance == null) {
			instance = new App();
		}
		return instance;
	}
}

Notice that:

  1. The constructor is private.
  2. The instance field is private and static

Structural

decorator

problem description
We want to add new behaviors to objects without modifying them (because we don’t want to modify them or maybe we can’t!).

solution using decorator pattern
Decorator lets you attach new behaviors to objects by placing these objects inside special wrapper objects that contain the behaviors.

ConcreteComponent component = new ConcreteComponent();
Decorator decorator = new Decorator(component);
decorator.someLogic();  //outputs:
// Some logic by decorator part 1
// Some logic by Component
// Some logic by decorator part 2
decorator.someDecoratorNewLogic(); // outputs:
// I can do logic without using the decorator component
interface Component {
	void someLogic ();
}

class ConcreteComponent implements Component{
	@Override
	public void someLogic () {
		System.out.println("Some logic by Component");
	}
}

class Decorator implements Component{
	private Component wrappee;
	
	public Decorator (Component wrappee) {
		this.wrappee = wrappee;
	}

	@Override
	public void someLogic () {
		System.out.println("Some logic by decorator part 1");
		wrappee.someLogic();
		System.out.println("Some logic by decorator part 2");
	}
	
	public void someDecoratorNewLogic () {
	    System.out.println("I can do logic without using the decorated component");
	}
}
notes
example class diagram
example with multiple decorators
ConcreteComponent component = new ConcreteComponent();
Decorator decorator1 = new ConcreteDecorator1(component);
decorator1.someLogic();
        
Decorator decorator2 = new ConcreteDecorator2(component);
decorator2.someLogic();
interface Component {
	void someLogic ();
}

class ConcreteComponent implements Component{
	@Override
	public void someLogic () {
		System.out.println("Some logic by Component");
	}
}


abstract class Decorator implements Component{
    
    protected Component wrappee;
    
    public Decorator (Component wrappee) {
		this.wrappee = wrappee;
	}
    
	@Override
	public void someLogic () {
	    wrappee.someLogic();
	}
}

class ConcreteDecorator1 extends Decorator{
    
    public ConcreteDecorator1 (Component wrappee) {
        super(wrappee);
    }
	
	@Override
	public void someLogic () {
		System.out.println("Some logic by Decorator1 part 1");
		wrappee.someLogic();
		System.out.println("Some logic by Decorator1 part 2");
	}
}

class ConcreteDecorator2 extends Decorator{
    
    public ConcreteDecorator2 (Component wrappee) {
        super(wrappee);
    }
	
	@Override
	public void someLogic () {
		System.out.println("Some different logic by Decorator2 part 1");
		wrappee.someLogic();
		System.out.println("Some different logic by Decorator2 part 2");
	}
}

class diagram

facade
problem description

You have an object that has to interact with a broad set of objects that belong to a sophisticated library. To do that you would normally initilize all the objects, execute methods and so on.

The result is that the business logic will be tightly coupled with the implementation of 3rd-party classes, making it hard to mantain.

using facade pattern
We use facade pattern to hide the complexity of the system.

ComputerFacade computer = new ComputerFacade();
computer.start();
interface CPU {
  void freeze ();
  void jump (int position);
  void execute ();
}

interface HardDrive {
  String read(int lba, int size);
}

interface Memory {
  void load(int position, String data);
}

class ComputerFacade {
	private CPU cpu;
	private Memory memory;
	private HardDrive hardDrive;

	public void start () {
		cpu.freeze();
		memory.load(kBootAddress, hardDrive.read(kBootSector, kSectorSize));
		cpu.jump(kBootAddress);
		cpu.execute();
	}
}
notes

facade & decorator
Facade wraps a whole system while Decorator wraps only a component of the system.

adapter
problem description

We want to allow objects with incompatible interfaces to work together.

Suppose you have an app that displays menus using XML data, like this.

interface IApp {
	void displayMenus (XmlData xmlData);
}

class App implements IApp {
	@Override
	public void displayMenus (XmlData xmlData) {
		// Displays menu using XML data
	}
}

One day, you want to enhance how you display menus, and you find the perfect UI library. The problem is that this library uses JsonData to display menus!

class FancyUIService {
	public void displayMenus (JsonData jsonData) {
		// Make use of the JsonData to fetch menus
	}
}

So how can we make the 2 components work together?

solution using adapter design pattern
XmlData xmlDataToDisplay = new XmlData();

IApp app = new App();
app.displayMenus(xmlDataToDisplay); // Show menu using old UI

IApp adapter = new FancyUIServiceAdapter();
adapter.displayMenus(xmlDataToDisplay); // Show menu using new fancy UI

class FancyUIServiceAdapter 
	extends FancyUIService 
	implements IApp{
	
	@Override
	public void displayMenus (XmlData xmlData) {
		super.displayMenus(convertXmlToJson(xmlData));
	}
	
	private JsonData convertXmlToJson (XmlData xmlData) {
		// Convert XmlData to JsonData and return it
	}
}

Note that if FancyUIService is final you can use composition instead of hineritance and do like this:


class FancyUIServiceAdapter implements IApp{
	
	private final FancyUIService fancyUIService;
	
	public FancyUIServiceAdapter () {
		this.fancyUIService = new FancyUIService();
	}
	
	@Override
	public void displayMenus (XmlData xmlData) {
		super.displayMenus(convertXmlToJson(xmlData));
	}
	
	private JsonData convertXmlToJson (XmlData xmlData) {
		// Convert XmlData to JsonData and return it
	}
}
notes

example class diagram

adapter & decorator
The main objective of the Adapter pattern is to adapt an interface to another, where this interface is expected by the client. In the example you have an interface IApp which is obsolete, the objective is to adapt FancyUIService to resemble an implementation of IApp.

Behavioral

observer

problem description
An object Subject has a state and one or more object(s) Observers wants to know when the Subject state changes.

solution 1. (bad) polling
polling means that Observer will constantly look at the state inside Subject. This solution is inefficient.

solution 2. (good) use observer pattern

This way, each Observer subscribes to a Subject and it’s the Subject who has the task of informing all of it’s Observers when the state changes.

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

// Subject we want to observe
class Subject { 
    public int state = 1;
    
    private List<Observer> observers = new ArrayList<>();
    
    public void addObserver (Observer observer) {
        observers.add(observer);
    }
    
    public void removeObserver (Observer observer) {
        observers.remove(observer);
    }

    public void notifyObservers () {
        for (Observer observer : this.observers) {
            observer.update();
        }
        // observers.forEach(Observer::update); // It's the same
    }
    
    public void someLogic () { // This logic can be even a setter for the state
        state = state * 2;
        notifyObservers(); // Be aware of this
    }

}

interface Observer  {
    void update ();
}

class ConcreteObserver1 implements Observer {
    
    private Subject sub;
    
    public ConcreteObserver1 (Subject sub) {
        this.sub = sub;
    }
    
    public void update () {
        System.out.println("Subject state is " + sub.state + " (from ConcreteObserver1)");
    }
}

class ConcreteObserver2 implements Observer {
    
    private Subject sub;
    
    public ConcreteObserver2 (Subject sub) {
        this.sub = sub;
    }
    
    public void update () {
        System.out.println("Subject state is " + sub.state + " (from ConcreteObserver2)");
    }
}

public class Client {
	public static void main (String[] args) {
		Subject s = new Subject();
		Observer observer1 = new ConcreteObserver1(s);
        Observer observer2 = new ConcreteObserver2(s);
		s.addObserver(observer1);
		s.addObserver(observer2);
		
		s.someLogic(); 
		// Will output: 
		// Subject state is 2 (from ConcreteSubscriber1)
		// Subject state is 2 (from ConcreteSubscriber2)
		
		s.someLogic(); 
        // Will output: 
		// Subject state is 4 (from ConcreteSubscriber1)
		// Subject state is 4 (from ConcreteSubscriber2)
	}
}
notes

(example) class diagram
The architecture described above is reflected in the class diagram below:

abstracting observer logic handling
You can abstract the Observer logic handling inside the Subject in an abstract class Subject inherit from, like this

abstract class AbstractSubject {
    private List<Observer> observers = new ArrayList<>();
    
    public void addObserver (Observer observer) {
        observers.add(observer);
    }
    
    public void removeObserver (Observer observer) {
        observers.remove(observer);
    }

    public void notifyObservers () {
        for (Observer observer : this.observers) {
            observer.update();
        }
        // observers.forEach(Observer::update); // It's the same
    }
}

class Subject extends AbstractSubject{
    public int state = 1;
    
    public void someLogic () { // This logic can be even a setter for the state
        state = state * 2;
        notifyObservers(); // Be aware of this
    }

}
publish-subscriber
problem description

The problem is the same as the observer pattern: we have a Subject we want to observe for it’s updates.

This time though there is a twist: in this case a message is not broadcasted to all observers but a subscriber can subscribe to different topics he is interested into.

solution using publish-subscriber pattern
MessageBroker broker = new MessageBroker();

// Subscribers si iscrivono a topic specifici
Subscriber sub1 = new ConcreteSubscriber1();
Subscriber sub2 = new ConcreteSubscriber2();

broker.subscribe("news", sub1);
broker.subscribe("news", sub2);
broker.subscribe("updates", sub2); // sub2 also subscribes to a different topic

Publisher publisher = new Publisher(broker);
publisher.sendMessage("news", "Breaking News!");
// ConcreteSubscriber1 received: Breaking News!
// ConcreteSubscriber2 received: Breaking News!
        
publisher.sendMessage("updates", "System update available.");
// ConcreteSubscriber2 received: System update available.
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

class Publisher {
    private MessageBroker broker;

    public Publisher(MessageBroker broker) {
        this.broker = broker;
    }

    public void sendMessage(String topic, String message) {
        broker.publish(topic, message);
    }
}

class MessageBroker {
    private Map<String, List<Subscriber>> subscribers = new HashMap<>();

    public void subscribe(String topic, Subscriber subscriber) {
        this.subscribers.putIfAbsent(topic, new ArrayList<>());
        this.subscribers.get(topic).add(subscriber);
    }

    public void publish(String topic, String message) {
        if (this.subscribers.containsKey(topic)) {
            for (Subscriber subscriber : subscribers.get(topic)) {
                subscriber.receive(message);
            }
        }
    }
}


interface Subscriber {
    void receive(String message);
}


class ConcreteSubscriber1 implements Subscriber {
    public void receive(String message) {
        System.out.println("ConcreteSubscriber1 received: " + message);
    }
}

class ConcreteSubscriber2 implements Subscriber {
    public void receive(String message) {
        System.out.println("ConcreteSubscriber2 received: " + message);
    }
}
notes

(example) class diagram
The architecture described above is reflected in the class diagram below:

strategy (and dependency injection)

problem description
We want to choose algorithms at runtime. For example we can have a Shop and we want to decide the discount algorithm (called with getTotal) at runtime.

Shop s = new Shop();
int total = s.getTotal(100);
System.out.println(total); // prints 90 because we are applying a discount of 10%
solution 1. use an enum (bad)
Shop s = new Shop(DiscountType.ABSOLUTE_DISCOUNT);
int total = s.getTotal(100);
System.out.println(total); // prints 90
enum DiscountType {
    NO_DISCOUNT,
    ABSOLUTE_DISCOUNT,
    PERCENTAGE_DISCOUNT
}

class Shop {
    private DiscountType dt;
    
    public int absoluteDiscount = 10;
    public int percentageDiscount = 20;
    
    public Shop (DiscountType dt) {
        this.dt = dt;
    }
    
    public void setDiscountType (DiscountType dt) {
        this.dt = dt;
    }
    
    public int getTotal (int originalPrice) {
        switch (dt) {
            case NO_DISCOUNT:
                return originalPrice;
            case ABSOLUTE_DISCOUNT:
                return originalPrice - absoluteDiscount;
            case PERCENTAGE_DISCOUNT:
                return originalPrice - (originalPrice*100/percentageDiscount);
        }
        return 0;
    }
    
}
solution 2. use strategy pattern (good)
DiscountStrategy ds = new AbsoluteDiscountStrategy(10);
Shop s = new Shop(ds);
int total = s.getTotal(100);
System.out.println(total); // prints 90
interface DiscountStrategy {
    int applyDiscount (int originalPrice);
}

class NoDiscountStrategy implements DiscountStrategy {
    @Override
    public int applyDiscount (int originalPrice) {
        return originalPrice;
    }
}

class AbsoluteDiscountStrategy implements DiscountStrategy {
    
    private int discount;
    
    public AbsoluteDiscountStrategy (int discount) {
        this.discount = discount;
    }
    
    @Override
    public int applyDiscount (int originalPrice) {
        return originalPrice - discount;
    }
}

class PercentaceDiscountStrategy implements DiscountStrategy {
    private int percentage;
    
    public PercentaceDiscountStrategy (int percentage) {
        this.percentage = percentage;
    }
    
    @Override
    public int applyDiscount (int originalPrice) {
        return originalPrice - (originalPrice - originalPrice*percentage/100);
    }
}

class Shop {
    private DiscountStrategy ds;
    
    public Shop (DiscountStrategy ds) {
        this.ds = ds;
    }
    
    public void setDiscountStrategy (DiscountStrategy ds) {
        this.ds = ds;
    }
    
    public int getTotal (int originalPrice) {
        return ds.applyDiscount(originalPrice);
    }
    
}
notes

class diagram

dependency injection
The difference between Strategy and Dependency Injection is little and is about what they are trying to achieve. The Strategy pattern is used in situations where you know that you want to swap out implementations. As an example, you might want to format data in different ways - you could use the strategy pattern to swap out an XML formatter or CSV formatter, etc.

Dependency Injection is different in that the user is not trying to change the runtime behaviour. Following the example above, we might be creating an XML export program that uses an XML formatter. Rather than structuring the code like this:

public class DataExporter() {
  XMLFormatter formatter = new XMLFormatter();
}

you would ‘inject’ the formatter in the constructor:

public class DataExporter {
  IFormatter formatter = null;

  public DataExporter(IDataFormatter dataFormatter) {
    this.formatter = dataFormatter;
  }
}

DataExporter exporter = new DataExporter(new XMLFormatter());

strategy & visitor
See this section on visitor pattern.

state

probelem description
We want to implement a finite state machine.

solution 1. use enum & if statement (bad)
Phone phone = new Phone();
		
JButton home = new JButton("Home");
home.addActionListener(e -> phone.onHomeButton());
				
JButton onAndOff = new JButton("on/off");
onAndOff.addActionListener(e -> phone.onPowerButton());
enum State {
    OFF,
    LOCKED,
    READY
}


class Phone {
    private State state;
    
    public Phone () {
        this.state = State.OFF;
    }

    
    public void onPowerButton () {
        if (this.state == State.OFF) {
            this.state = State.LOCKED;
        } else if (this.state == State.LOCKED) {
            this.State = State.OFF;
        } else if (this.State == State.READY) {
            this.state = State.OFF;
        }
    }
    
    public void onHomeButton () {
        if (this.state == State.OFF) {
            this.state = State.LOCKED;
        } else if (this.state == State.LOCKED) {
            this.State = State.READY;
        } else if (this.State == State.READY) { }
    }
   
}

solution 2. use state design pattern (good)
We will interact with the phone using only the onHomeButton method and the onPowerButton method, and the internal state will change accordingly.

Phone phone = new Phone();
		
JButton home = new JButton("Home");
home.addActionListener(e -> phone.getState().onHomeButton());
				
JButton onAndOff = new JButton("on/off");
onAndOff.addActionListener(e -> phone.getState().onPowerButton());
class Phone {
    private State state;
    
    public Phone () {
        state = new OffState(this);
    }
    
    public State getState () {
        return this.state;
    }
    
    public void setState (State state) {
        this.state = state;
    }
}

abstract class State {
    protected Phone phone;
    
    public State (Phone phone) {
        this.phone = phone;
    }
    
    public abstract void onHomeButton ();
    public abstract void onPowerButton ();
    
}

class OffState extends State {
    public OffState (Phone phone) {
        super(phone);
    }
    
    @Override
    public void onHomeButton () {
        phone.setState(new LockedState(phone));
    }
    
    @Override
    public void onPowerButton () {
        phone.setState(new LockedState(phone));
    }
}

class LockedState extends State {
    public LockedState (Phone phone) {
        super(phone);
    }
    
    @Override
    public void onHomeButton () {
        phone.setState(new ReadyState(phone));
    }
    
    @Override
    public void onPowerButton () {
        phone.setState(new OffState(phone));
    }
}

class ReadyState extends State {
    public ReadyState (Phone phone) {
        super(phone);
    }
    
    @Override
    public void onHomeButton () { }
    
    @Override
    public void onPowerButton () {
        phone.setState(new OffState(phone));
    }
}
notes

class diagram

state & strategy
The state pattern can be considered as an extension of strategy. The class diagram are pretty much the same. They both use composition.

Differences:

  1. strategys are completely independent and unaware of each other, whereas states can be dependent.
  2. strategy pattern is about having different implementation that accomplish the same thing.
visitor
problem description

You have one (or more) class(es) where you want to add a new functionality. For example you have a Dot class and a Circle class, like this:

interface Shape {
    public void draw ();
}

class Dot implements Shape{
    float x = 5, y = 8;
    public void draw () { }
}

class Circle implements Shape{
    float radius;
    public void draw () { }
}

Now you want to add an export functionality. You can do it in two ways.

solution 1. adding `export` functionality directly inside the 2 classes (good)
interface Shape {
    public void draw ();
	public void export ();
}

class Dot implements Shape {
    float x = 5, y = 8;
    public void draw () { }
	public void export () {
		System.out.println("Exporting Dot, x = " + d.x + ", y = " + d.y);
	}
}

class Circle implements Shape{
    float radius = 7;
    public void draw () { }
	public void export () {
		System.out.println("Exporting Circle, radius = " + radius);
	}
}

public class Client {
	public static void main(String[] args) {
		Shape dot = new Dot();
		dot.export(); 
		// outputs correctly: Exporting Dot, x = 5.0, y = 8.0
	
	    Shape [] shapeArray = new Shape[] {new Circle(), new Dot()};
	    for(Shape shape: shapeArray){
	        shape.export();
	    }
		// outputs correctly: 
		// Exporting Circle, radius = 7.0
		// Exporting Dot, x = 5.0, y = 8.0
	    
	}
}

This one works fine, but we have modified the code of Shape and Dot. What if we do not want to do that?

solution 2. adding new class handling `export` functionality for all shapes (bad)

// Not touching Shapes
interface Shape {
    public void draw ();
}

class Dot implements Shape {
    float x = 5, y = 8;
    public void draw (){ }
}

class Circle implements Shape {
    float radius;
    public void draw (){ }
}

class Exporter {
    void export(Shape s) {
        System.out.println("Exporting Shape");
    }
    void export(Dot d) {
        System.out.println("Exporting Dot, x = " + d-x + ", d.y = " + y);
    }
    void export(Circle c) {
        System.out.println("Exporting Circle, radius = " + c.radius);
    }
}

public class Client {
	public static void main(String[] args) {
	    Shape s = new Dot(); 
	    Exporter ex = new Exporter();
	    ex.export(s);
		// outputs INcorrectly: Exporting Shape
	    	    
	    Shape [] shapeArray = new Shape[] {new Circle(), new Dot()};
	    for(Shape shape: shapeArray){
	        ex.export(shape);
	    }
		// outputs INcorrectly: 
		// Exporting Shape
		// Exporting Shape
	}
}

You can see that in this case it does not work. Pay attention, it’s obvious that if you do

Dot s = new Dot();
Exporter ex = new Exporter();
ex.export(s);

This one will output correctly Exporting Dot, x = 5.0, y = 8.0 because at static time we know that the shape is of type Dot.

solution 2. using visitor pattern (good)
interface Shape {
    public void draw ();
    public void accept(ExportVisitor visitor);
}

class Dot implements Shape{
    float x = 5, y = 8;
    public void draw (){ }
    
    public void accept(ExportVisitor visitor){
        visitor.visit(this);
    }
}

class Circle implements Shape{
    float radius = 8;
    public void draw (){ }
    
    public void accept(ExportVisitor visitor){
        visitor.visit(this);
    }
}

class ExportVisitor {
    void visit(Dot d){
        System.out.println("Exporting Dot, x = " + d.x + ", y = " + d.y);
    }
    void visit(Circle c){
        System.out.println("Exporting Circle, radius = " + c.radius);
    }
}


public class Client{
	public static void main(String[] args) {
		
		Shape s = new Dot(); 
	    ExportVisitor ex = new ExportVisitor();
	    s.accept(ex);
		// outputs correctly: Exporting Dot, x = 5.0, y = 8.0
	    	    
	    Shape [] shapeArray = new Shape[] {new Circle(), new Dot()};
	    for(Shape shape: shapeArray){
	        shape.accept(ex);
	    }
		// outputs correctly: 
		// Exporting Circle, radius = 7.0
		// Exporting Dot, x = 5.0, y = 8.0
	}
}

Notice how we are in fact changing the Dot and Circle code, but notice how the numbers of line changed inside a class is always constant, because you only just need to accept the visitor, whereas the Visitor logic might be big (now it’s just printing).

notes
adding another operation with visitor

You might be thinking that if you need to add another operation, for example a Transform operation, You have to

  1. Create TransformVisitor
  2. Add an accept(TransformVisitor) inside all shapes.

But this is not the case, you can just abstract all the visitors in an interface and create just 1 accept method for all visitors, like this:

Shape s = new Dot(); 
ExportVisitor ex = new ExportVisitor();
TransformVisitor tr = new TransformVisitor();
s.accept(ex);
s.accept(tr);
// outputs correctly: 
// Exporting Dot, x = 5.0, y = 8.0
// Transforming Dot, 2*x = 10.0, 2*y = 16.0
	    	    
Shape [] shapeArray = new Shape[] {new Circle(), new Dot()};
for(Shape shape: shapeArray){
	shape.accept(ex);
	shape.accept(tr);
}
// outputs correctly: 
// Exporting Circle, radius = 7.0
// Transforming Circle, 2*radius = 16.0
// Exporting Dot, x = 5.0, y = 8.0
// Transforming Dot, 2*x = 10.0, 2*y = 16.0
interface Shape {
    public void draw ();
    public void accept(Visitor visitor); // Notice how now it accepts a Visitor (not a specific one)
}

class Dot implements Shape{
    float x = 5, y = 8;
    public void draw (){ }
    
    public void accept(Visitor visitor){
        visitor.visit(this);
    }
}

class Circle implements Shape{
    float radius = 8;
    public void draw (){ }
    
    public void accept(Visitor visitor){
        visitor.visit(this);
    }
}


interface Visitor {
    void visit (Dot d);
    void visit (Circle c);
}

class ExportVisitor implements Visitor{
    public void visit(Dot d){
        System.out.println("Exporting Dot, x = " + d.x + ", y = " + d.y);
    }
    public void visit(Circle c){
        System.out.println("Exporting Circle, radius = " + c.radius);
    }
}

class TransformVisitor implements Visitor{
    public void visit(Dot d){
        System.out.println("Tranforming Dot, 2*x = " + (2*d.x) + ", 2*y = " + (2*d.y));
    }
    public void visit(Circle c){
        System.out.println("Tranforming Circle, 2*radius = " + (2*c.radius));
    }
}

public class Client{
	public static void main(String[] args) {
		
	}
}

class diagram
The architecture described above is reflected in the class diagram below:

strategy & visitor
They both delegate the an operation to an extern object. The class diagrams are the following:

They have 2 different intent:

  • Visitor: rapresent an operation to execute on an object structure.
  • Strategy: rapresent different type of operations to be executed in an object.
iterator

problem description
Given a component containing an aggregate of elements, we want a way to iterate over it without knowing the underlying data structure (array, hashmap, graph, tree…).

class Social1 {
    private List<String> emails = new ArrayList<>();
    
    public Social1 () { }
    
    public void addUser (String email) {
        this.emails.add(email);
    }
    
    public List getEmails () {
        return emails;
    }
}

The problem of this code is that if i change the implementation of emails from List to HashSet clients will break. I can change the type to be a Collection but again, if i change the implementation to be an array, clients will break again.

solution using iterator pattern
Iterator pattern gives a way of accessing aggregate elements in a sequential way without exposing underlying representation.

It exposes only 2 interface method: hasNext and getNext.

Social1 s1 = new Social1();
s1.addUser("luigi");
s1.addUser("cennini");
Social1Iterator s1it = s1.createIterator();
	    
while (s1it.hasNext()) {
	String user = s1it.getNext();
	System.out.println(user);
}
import java.util.ArrayList;
import java.util.List;

class Social1 {
    private List<String> emails = new ArrayList<>();
    
    public Social1 () { }
    
    public void addUser (String email) {
        this.emails.add(email);
    }
    
    public Social1Iterator createIterator () {
        return new Social1Iterator(emails);
    }
}

class Social1Iterator {
    private List<String> emails;
    private int currentPosition = 0;
    
    public Social1Iterator (List<String> emails) {
        this.emails = emails;
    }
    
    public boolean hasNext () {
        return currentPosition < emails.size();
    }
    
    public String getNext () {
        if (!hasNext()) {
            return null;
        }
        String email = emails.get(currentPosition);
        currentPosition ++;
        return email;
    }
    
}
notes

(example) class diagram

Another example, tree iterator
Having a Tree like structure, i want an iterator to easily iterate over the structure in pre order and in order traversal ways.

Node root = new Node(10);   //                   10
root.left = new Node(20);   //              20        30
root.right = new Node(30);  //          50     80
root.left.left = new Node(50);
root.left.right = new Node(80);
		
		
System.out.println("In order traversal: ");
TreeIterator ti1 = root.createPreOrderIterator();
while (ti1.hasNext()) {
	Node current = ti1.getNext();
	System.out.println(current.val);
}
//prints: 10 20 50 80 30
		
System.out.println("Post order traversal: ");
TreeIterator ti2 = root.createInOrderIterator();
while (ti2.hasNext()) {
	Node current = ti2.getNext();
	System.out.println(current.val);
}
//prints: 50 20 80 10 30
import java.util.Stack;

class Node {
    public int val;
    public Node left = null, right = null;
    
    public Node (int val){
        this.val = val;
    }
    
    
    public TreeIterator createPreOrderIterator () {
        return new PreOrderIterator(this);
    }
    
    public TreeIterator createInOrderIterator () {
        return new InOrderOrderIterator(this);
    }
}


interface TreeIterator {
    boolean hasNext ();
    Node getNext ();
}

class PreOrderIterator implements TreeIterator{
    
    private Stack<Node> stack = new Stack<>();
    
    public PreOrderIterator(Node root) {
        stack.push(root);
    }
    
    @Override
    public boolean hasNext() {
        return !stack.isEmpty();
    }
    
    @Override
    public Node getNext() {
        if (!hasNext()) {
            return null;
        }
        Node current = stack.pop();
        if (current.right != null) {
            stack.push(current.right);
        }
        if (current.left != null) {
            stack.push(current.left);
        }
        return current;
    }
    
}


class InOrderOrderIterator implements TreeIterator{
    
    private Stack<Node> stack = new Stack<>();
    private Node current;

    
    public InOrderOrderIterator(Node root) {
        this.current = root;
    }
    
    @Override
    public boolean hasNext() {
        return !stack.isEmpty() || current != null;
    }
    
    @Override
    public Node getNext() {
        while (current != null) {
            stack.push(current);
            current = current.left;
        }
        if (!stack.isEmpty()) {
            Node node = stack.pop();
            current = node.right;
            return node;
        }
        return null;
    }
    
}

(official) class diagram

chain of responsabilities

problem description
An object wants to pass the responsability of performing an action to a chain of other objects.

solution using chain of responsabilities pattern
Each object in the chain tries to handle the request, if it can’t, the request is passed down in the chain.

StringFinder finder = new EqualsStringFinder(
	new EqualsNoCaseStringFinder(
		new PrefixStringFinder(
			new SuffixStringFinder(null))));
	System.out.println(
		finder.match("hello world", "hello world"));
abstract class StringFinder {
    private StringFinder next;
    public StringFinder (StringFinder next) {
        this.next = next;
    }

    public boolean match (String aString, String anotherString) {
        if (next != null) {
            return next.match(aString, anotherString);
        }
        return false;
    }
}

class EqualsStringFinder extends StringFinder {
    public EqualsStringFinder(StringFinder next) {
        super(next);
    }
    
    @Override
    public boolean match(String aString, String anotherString) {
        if (aString.equals(anotherString))
            return true;
        return super.match(aString, anotherString);
    }
}

class EqualsNoCaseStringFinder extends StringFinder {
    
    public EqualsNoCaseStringFinder(StringFinder next) {
        super(next);
    }
    
    @Override
    public boolean match(String aString, String anotherString) {
        if (aString.equalsIgnoreCase(anotherString))
            return true;
        return super.match(aString, anotherString);
    }
}


class PrefixStringFinder extends StringFinder {
    public PrefixStringFinder(StringFinder next) {
        super(next);
    }
    
    @Override
    public boolean match(String aString, String anotherString) {
        if (aString.startsWith(anotherString))
            return true;
        return super.match(aString, anotherString);
    }
    
} 

class SuffixStringFinder extends StringFinder {
    public SuffixStringFinder(StringFinder next) {
        super(next);
    }
    
    @Override
    public boolean match(String aString, String anotherString) {
        if (aString.endsWith(anotherString))
            return true;
        return super.match(aString, anotherString);
    }
}
notes

example class diagram

implementation where the forwarding logic is implemented in the abstract class
StringFinder finder = new EqualsStringFinder(
	new EqualsNoCaseStringFinder(
		new PrefixStringFinder(
			new SuffixStringFinder(null))));
	System.out.println(
		finder.match("hello world", "hello world"));
abstract class StringFinder {
    private StringFinder next;
    public StringFinder (StringFinder next) {
        this.next = next;
    }

    public boolean match (String aString, String anotherString) {
        if (doMatch(aString, anotherString)) {
            return true;
        } else if (next != null) {
            return next.match(aString, anotherString);
        }
        return false;
    }
    
    
    protected abstract boolean doMatch (String aString, String anotherString);
}

class EqualsStringFinder extends StringFinder {
    public EqualsStringFinder(StringFinder next) {
        super(next);
    }
    
    @Override
    public boolean doMatch(String aString, String anotherString) {
        return aString.equals(anotherString);
    }
}

class EqualsNoCaseStringFinder extends StringFinder {
    
    public EqualsNoCaseStringFinder(StringFinder next) {
        super(next);
    }
    
    @Override
    public boolean doMatch(String aString, String anotherString) {
        return aString.equalsIgnoreCase(anotherString);
    }
}


class PrefixStringFinder extends StringFinder {
    public PrefixStringFinder(StringFinder next) {
        super(next);
    }
    
    @Override
    public boolean doMatch(String aString, String anotherString) {
        return aString.startsWith(anotherString);
    }
    
} 

class SuffixStringFinder extends StringFinder {
    public SuffixStringFinder(StringFinder next) {
        super(next);
    }
    
    @Override
    public boolean doMatch(String aString, String anotherString) {
        return aString.endsWith(anotherString);
    }
}