Design Patterns

Overview

Java Design Patterns - Creational, Structural & Behavioral design patterns.

Github: https://github.com/gitorko/project62

Creational Design Patterns

Provides way to create objects while hiding the creation logic.

1. Singleton Pattern

Singleton pattern ensures that only one instance of the class exists in the java virtual machine.

A singleton class has these common features

  • private constructor to restrict creation of instance by other classes.
  • private static variable of the same class.
  • public static method to get instance of class.

We will first look at eager loaded singleton. This is costly as object is created at time of class loading,also no scope for exception handling if instantiation fails.

 1package com.demo.project62.singleton;
 2
 3public class EagerLoadedSingleton {
 4
 5    private static final EagerLoadedSingleton instance = new EagerLoadedSingleton();
 6
 7    private EagerLoadedSingleton() {
 8    }
 9
10    public static void main(String[] args) {
11        System.out.println(EagerLoadedSingleton.getInstance().hello());
12    }
13
14    public static EagerLoadedSingleton getInstance() {
15        return instance;
16    }
17
18    public String hello() {
19        return ("Hello from EagerLoadedSingleton!");
20    }
21}

This can be modified to static block singleton which provides room for handling exception.

 1package com.demo.project62.singleton;
 2
 3public class StaticBlockSingleton {
 4
 5    private static final StaticBlockSingleton instance;
 6
 7    static {
 8        try {
 9            instance = new StaticBlockSingleton();
10        } catch (Exception e) {
11            throw new RuntimeException("Exception occured in creatingsingleton instance");
12        }
13    }
14
15    private StaticBlockSingleton() {
16    }
17
18    public static void main(String[] args) {
19        System.out.println(StaticBlockSingleton.getInstance().hello());
20    }
21
22    public static StaticBlockSingleton getInstance() {
23        return instance;
24    }
25
26    public String hello() {
27        return ("Hello from StaticBlockSingleton!");
28    }
29}

The next step is to use lazy initialization singleton as creating singleton at class loading time and not using it will be costly.

 1package com.demo.project62.singleton;
 2
 3public class LazyLoadedSingleton {
 4
 5    private static LazyLoadedSingleton instance;
 6
 7    private LazyLoadedSingleton() {
 8    }
 9
10    public static void main(String[] args) {
11        System.out.println(LazyLoadedSingleton.getInstance().hello());
12    }
13
14    public static LazyLoadedSingleton getInstance() {
15        if (instance == null) {
16            instance = new LazyLoadedSingleton();
17        }
18        return instance;
19    }
20
21    public String hello() {
22        return ("Hello from LazyLoadedSingleton!");
23    }
24}

However this is not thread safe as in multithread environment 2 threads can get 2 different instances of the object. So lets make this thread safe. Notice we introduced synchronized keyword on the getInstance method.

 1package com.demo.project62.singleton;
 2
 3public class ThreadSafeSingleton {
 4
 5    private static ThreadSafeSingleton instance;
 6
 7    private ThreadSafeSingleton() {
 8    }
 9
10    public static void main(String[] args) {
11        System.out.println(ThreadSafeSingleton.getInstance().hello());
12    }
13
14    public static synchronized ThreadSafeSingleton getInstance() {
15        if (instance == null) {
16            instance = new ThreadSafeSingleton();
17        }
18        return instance;
19    }
20
21    public String hello() {
22        return ("Hello from ThreadSafeSingleton!");
23    }
24}

The above program is thread safe but reduces performance as each thread waits to enter the synchronized block. We now fix that by introducing double check locking. Notice that we removed the synchronized keyword on the getInstance method and moved it inside the method. We now perform 2 if checks on the instance.

 1package com.demo.project62.singleton;
 2
 3public class ThreadSafeSingletonDoubleCheckLock {
 4
 5    private static ThreadSafeSingletonDoubleCheckLock instance;
 6
 7    private ThreadSafeSingletonDoubleCheckLock() {
 8    }
 9
10    public static void main(String[] args) {
11        System.out.println(ThreadSafeSingletonDoubleCheckLock.getInstance().hello());
12    }
13
14    public static ThreadSafeSingletonDoubleCheckLock getInstance() {
15        if (instance == null) {
16            synchronized (ThreadSafeSingletonDoubleCheckLock.class) {
17                if (instance == null) {
18                    instance = new ThreadSafeSingletonDoubleCheckLock();
19                }
20            }
21
22        }
23        return instance;
24    }
25
26    public String hello() {
27        return ("Hello from ThreadSafeSingleton!");
28    }
29}

Using reflection all previous singleton implementation can be broken

 1package com.demo.project62.singleton;
 2
 3import java.lang.reflect.Constructor;
 4import java.lang.reflect.InvocationTargetException;
 5
 6public class BreakSingletonByReflection {
 7
 8    public static void main(String[] args) {
 9        new BreakSingletonByReflection().testSingleton();
10    }
11
12    public void testSingleton() {
13
14        ThreadSafeSingletonDoubleCheckLock instanceOne = ThreadSafeSingletonDoubleCheckLock.getInstance();
15        ThreadSafeSingletonDoubleCheckLock instanceTwo = null;
16        try {
17            Constructor[] constructors = ThreadSafeSingletonDoubleCheckLock.class.getDeclaredConstructors();
18            for (Constructor constructor : constructors) {
19                constructor.setAccessible(true);
20                instanceTwo = (ThreadSafeSingletonDoubleCheckLock) constructor.newInstance();
21                break;
22            }
23        } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
24                | InvocationTargetException ex) {
25            ex.printStackTrace();
26        }
27        if (instanceOne.hashCode() != instanceTwo.hashCode()) {
28            System.out.println("Singleton broken!");
29        }
30    }
31}

To safeguard against reflection we will throw RuntimeException in the constructor. We will introduce the volatile keyword to make it even more thread safe.

How volatile works in java? The volatile keyword in Java is used as an indicator to Java compiler and Thread that do not cache value of this variable and always read it from main memory. Java volatile keyword also guarantees visibility and ordering, write to any volatile variable happens before any read into the volatile variable. It also prevents compiler or JVM from the reordering of code.

If we do not make the instance variable volatile than the Thread which is creating instance of Singleton is not able to communicate to the other thread, that the instance has been created until it comes out of the Singleton block, so if Thread A is creating Singleton instance and just after creation lost the CPU, all other thread will not be able to see value of instance as not null and they will believe its still null. By adding volatile java will not read the variable into thread context local memory and instead read it from the main memory each time.

 1package com.demo.project62.singleton;
 2
 3public class SingletonDefendReflection {
 4
 5    private static volatile SingletonDefendReflection instance;
 6
 7    private SingletonDefendReflection() {
 8        if (instance != null) {
 9            throw new RuntimeException("Use get instance to create object!");
10        }
11    }
12
13    public static void main(String[] args) {
14        System.out.println(SingletonDefendReflection.getInstance().hello());
15    }
16
17    public static SingletonDefendReflection getInstance() {
18        if (instance == null) {
19            synchronized (SingletonDefendReflection.class) {
20                if (instance == null) {
21                    instance = new SingletonDefendReflection();
22                }
23            }
24        }
25        return instance;
26    }
27
28    public String hello() {
29        return ("Hello from ThreadSafeSingleton!");
30    }
31}

To defend against reflection you can also use Enum based singleton, The disadvantage is you cant do lazy loading, you cant extend the singleton.

 1package com.demo.project62.singleton;
 2
 3public enum EnumSingleton {
 4
 5    INSTANCE;
 6
 7    public static void main(String[] args) {
 8        System.out.println(EnumSingleton.INSTANCE.hello());
 9    }
10
11    public String hello() {
12        return ("Hello from EnumSingleTon!");
13    }
14
15}

There is another approach of writing a singleton called Bill Pugh Singleton implementation which uses static inner helper class instead of using synchronized keyword.

 1package com.demo.project62.singleton;
 2
 3
 4public class BillPughSingleton {
 5
 6    private BillPughSingleton() {
 7    }
 8
 9    public static void main(String[] args) {
10        BillPughSingleton.getInstance().hello();
11    }
12
13    public static BillPughSingleton getInstance() {
14        return SingletonHelper.INSTANCE;
15    }
16
17    public String hello() {
18        return "Hello from BillPughSingleton";
19    }
20
21    private static class SingletonHelper {
22        private static final BillPughSingleton INSTANCE = new BillPughSingleton();
23    }
24}

In a distributed systems a singleton needs to be serialized and restored from store later and care must be taken to ensure that new instance is not created and the same instance that was serialized is restored. Notice the method readResolve if this method is removed then the singleton design breaks during de-serialization.

 1package com.demo.project62.singleton;
 2
 3import java.io.FileInputStream;
 4import java.io.FileOutputStream;
 5import java.io.ObjectInput;
 6import java.io.ObjectInputStream;
 7import java.io.ObjectOutput;
 8import java.io.ObjectOutputStream;
 9import java.io.Serializable;
10
11public class SerializedSingleton implements Serializable {
12
13    private static final long serialVersionUID = -1L;
14
15    private SerializedSingleton() {
16    }
17
18    public static void main(String[] args) throws Exception {
19
20        SerializedSingleton instanceOne = SerializedSingleton.getInstance();
21        ObjectOutput out = new ObjectOutputStream(new FileOutputStream("filename.ser"));
22        out.writeObject(instanceOne);
23        out.close();
24
25        ObjectInput in = new ObjectInputStream(new FileInputStream("filename.ser"));
26        SerializedSingleton instanceTwo = (SerializedSingleton) in.readObject();
27        in.close();
28        if (instanceOne.hashCode() != instanceTwo.hashCode()) {
29            System.out.println("Singleton broken!");
30        } else {
31            System.out.println(instanceOne.getInstance().hello());
32        }
33
34    }
35
36    public static SerializedSingleton getInstance() {
37        return SingletonHelper.instance;
38    }
39
40    public String hello() {
41        return ("Hello from singleton!");
42    }
43
44    protected Object readResolve() {
45        return getInstance();
46    }
47
48    private static class SingletonHelper {
49        private static final SerializedSingleton instance = new SerializedSingleton();
50    }
51
52}

A singleton example within java sdk is the Runtime class for garbage collection.

 1package com.demo.project62.singleton;
 2
 3public class RuntimeSingleton {
 4    public static void main(String[] args) {
 5        Runtime singleton1 = Runtime.getRuntime();
 6        singleton1.gc();
 7        Runtime singleton2 = Runtime.getRuntime();
 8        if (singleton1 == singleton2) {
 9            System.out.println("Singleton!");
10        } else {
11            System.out.println("Not Singleton!");
12        }
13    }
14}

Why not use a static class instead of writing a singleton class? Because static class doesnt guarantee thread safety.

Can i have parameters in a singleton? A singleton constructor cant take parameters that violates the rule of singleton. If there are parameters then it classifies as a factory pattern.

If singleton is unique instance per JVM instance how does it work in a tomcat server which can have 2 instances of same web application deployed on it. Since the applications still run on single JVM will they share the singleton? In this case both web applications will get their own instance of singleton because of class loader visibility.Tomcat uses individual class loaders for webapps. However if both application request a JRE or Tomcat singleton eg: Runtime then both get the same singleton.

2. Factory Pattern

Factory design pattern is used when we have a super class with multiple sub-classes and based on input, we need to return one of the sub-class. The main method doesnt know the details of instantiating a object its deferred to the factory subclass. Factory calls the new operator.

 1package com.demo.project62.factory;
 2
 3enum AnimalType {
 4    DOG, DUCK, CAT;
 5}
 6
 7interface Animal {
 8    public String sound();
 9}
10
11public class Main {
12
13    public static void main(String[] args) {
14        Animal animal = Factory.getAnimal(AnimalType.CAT);
15        System.out.println(animal.sound());
16    }
17}
18
19class Duck implements Animal {
20
21    @Override
22    public String sound() {
23        return "Quak!";
24    }
25}
26
27class Dog implements Animal {
28
29    @Override
30    public String sound() {
31        return "Bark!";
32    }
33}
34
35class Cat implements Animal {
36
37    @Override
38    public String sound() {
39        return "Meow!";
40    }
41}
42
43class Factory {
44    public static Animal getAnimal(AnimalType type) {
45        switch (type) {
46            case DOG:
47                return new Dog();
48            case CAT:
49                return new Cat();
50            case DUCK:
51                return new Duck();
52            default:
53                return null;
54        }
55    }
56}

3. Abstract Factory Pattern

Abstract factory pattern is similar to Factory pattern and it’s factory of factories. In factory pattern we used switch statement to decide which object to return in abstract factory we remove the if-else/switch block and have a factory class for each sub-class.

 1package com.demo.project62.abstractfactory;
 2
 3interface Animal {
 4    public String sound();
 5}
 6
 7interface BaseFactory {
 8    public Animal createAnimal();
 9}
10
11public class Main {
12
13    public static void main(String[] args) {
14        Animal animal = AbstractFactory.getAnimal(new DogFactory());
15        System.out.println(animal.sound());
16    }
17}
18
19class Duck implements Animal {
20    @Override
21    public String sound() {
22        return "Quak!";
23    }
24}
25
26class Dog implements Animal {
27    @Override
28    public String sound() {
29        return "Bark!";
30    }
31}
32
33class Cat implements Animal {
34    @Override
35    public String sound() {
36        return "Meow!";
37    }
38}
39
40class AbstractFactory {
41    public static Animal getAnimal(BaseFactory bf) {
42        return bf.createAnimal();
43    }
44}
45
46class DuckFactory implements BaseFactory {
47    @Override
48    public Animal createAnimal() {
49        return new Duck();
50    }
51}
52
53class DogFactory implements BaseFactory {
54    @Override
55    public Animal createAnimal() {
56        return new Dog();
57    }
58}
59
60class CatFactory implements BaseFactory {
61    @Override
62    public Animal createAnimal() {
63        return new Cat();
64    }
65}

4. Builder Pattern

Builder pattern is used to build a complex object with lot of attributes. It becomes difficult to pass the correct type in correct order to a constructor when there are many attributes. If some of the attributes are optional then there is overhead of having to pass null each time to the constructor or having to write multiple constructors(telescoping). Notice that in the example below builder pattern returns immutable object hence no setter methods exist. Notice the static inner class you can write an external class as well if you choose not to modify an existing class. Notice the private constructor of the Dog class as the only way to create an instance is via Builder. The name of dog and breed are the only mandatory fields this defines a contract that a dog object atleast needs these 2 attributes.

 1package com.demo.project62.builder;
 2
 3import lombok.Getter;
 4import lombok.ToString;
 5
 6public class Main {
 7
 8    public static void main(String[] args) {
 9        Dog dog1 = new Dog.DogBuilder("rocky", "German Sheperd").setColor("Grey").setAge(6).setWeight(40.5).build();
10        System.out.println(dog1);
11        Dog dog2 = new Dog.DogBuilder("rocky", "German Sheperd").build();
12        System.out.println(dog2);
13    }
14
15}
16
17@Getter
18@ToString
19class Dog {
20
21    String name;
22    String breed;
23    String color;
24    int age;
25    double weight;
26
27    private Dog(DogBuilder builder) {
28        this.name = builder.name;
29        this.breed = builder.breed;
30        this.color = builder.color;
31        this.age = builder.age;
32        this.weight = builder.weight;
33    }
34
35    @Getter
36    public static class DogBuilder {
37
38        String name;
39        String breed;
40        String color;
41        int age;
42        double weight;
43
44        public DogBuilder(String name, String breed) {
45            this.name = name;
46            this.breed = breed;
47        }
48
49        public Dog build() {
50            return new Dog(this);
51        }
52
53        public DogBuilder setColor(String color) {
54            this.color = color;
55            return this;
56        }
57
58        public DogBuilder setAge(int age) {
59            this.age = age;
60            return this;
61        }
62
63        public DogBuilder setWeight(double weight) {
64            this.weight = weight;
65            return this;
66        }
67    }
68}

Output:

1Dog(name=rocky, breed=German Sheperd, color=Grey, age=6, weight=40.5)
2Dog(name=rocky, breed=German Sheperd, color=null, age=0, weight=0.0)

Using lombok @Builder annotation you can reduce the code further

 1package com.demo.project62.builder.other;
 2
 3import lombok.Builder;
 4import lombok.Getter;
 5import lombok.ToString;
 6
 7public class Main {
 8    public static void main(String[] args) {
 9        Dog dog1 = Dog.builder().name("Rocky").breed("German Sheperd").build();
10        System.out.println(dog1);
11    }
12}
13
14@Builder
15@Getter
16@ToString
17class Dog {
18
19    String name;
20    String breed;
21    String color;
22    int age;
23    @Builder.Default
24    double weight = 30.0;
25}

Output:

1Dog(name=Rocky, breed=German Sheperd, color=null, age=0, weight=30.0)

An example in the java SDK is the StringBuilder class.

5. Prototype Pattern

Prototype pattern is used when the object creation is expensive. Instead of creating a new object you can copy the original object using clone and then modify it according to your needs. Prototype design pattern mandates that the object which you are copying should provide the copying feature, it should not be done by any other class. Decision to use shallow or deep copy of the object attributes is a design decision a shallow copy just copies immediate property and deep copy copies all object references as well. Notice we dont use new to create prototype objects after the first instance is created. Prototype avoid subclassing.

 1package com.demo.project62.prototype;
 2
 3import java.util.ArrayList;
 4import java.util.List;
 5
 6import lombok.AllArgsConstructor;
 7import lombok.Data;
 8
 9public class Main {
10
11    public static void main(String[] args) throws CloneNotSupportedException {
12        Employees emps = new Employees(new ArrayList<>());
13        emps.seedData();
14        Employees dataSet1 = (Employees) emps.clone();
15        Employees dataSet2 = (Employees) emps.clone();
16
17        System.out.println("dataSet1 size: " + dataSet1.getEmpList().size());
18        System.out.println("dataSet2 size: " + dataSet2.getEmpList().size());
19
20        dataSet2.getEmpList().add("jhon");
21        System.out.println("dataSet1 size: " + dataSet1.getEmpList().size());
22        System.out.println("dataSet2 size: " + dataSet2.getEmpList().size());
23    }
24
25}
26
27@AllArgsConstructor
28@Data
29class Employees implements Cloneable {
30
31    private List<String> empList;
32
33    public void seedData() {
34        //Invoke a remote call and fetch data and load it to list. The fetch is costly op.
35        for (int i = 0; i < 100; i++) {
36            empList.add("employee_" + i);
37        }
38    }
39
40    @Override
41    public Object clone() throws CloneNotSupportedException {
42        List<String> temp = new ArrayList<>();
43        for (String s : this.empList) {
44            temp.add(s);
45        }
46        return new Employees(temp);
47    }
48}

Output:

1dataSet1 size: 100
2dataSet2 size: 100
3dataSet1 size: 100
4dataSet2 size: 101

You can also create a registry to stored newly created objects when there are different types of objects and lookup against the registry when you want to clone objects.

Structural Design Patterns

Deal with class and object composition. Provide different ways to create a class structure, using inheritance and composition to create a large object from small objects

1. Adapter Pattern

Adapter pattern is used when two unrelated interfaces need to work together. There is a AlienCraft which has different type of fire & scan api that takes additional parameter compared to the human readable ship interface. However by writing the adapter we map the appropriate functions for fire and scan.

 1package com.demo.project62.adapter;
 2
 3import lombok.AllArgsConstructor;
 4
 5interface Ship {
 6    public void scan();
 7
 8    public void fire();
 9}
10
11public class Main {
12
13    public static void main(String[] args) {
14        SpaceShipAdapter shipAdapter = new SpaceShipAdapter(new AlienCraft());
15        shipAdapter.scan();
16        shipAdapter.fire();
17    }
18}
19
20class AlienCraft {
21    public void drakarys() {
22        System.out.println("Firing weapon");
23    }
24
25    public void jorarghugon() {
26        System.out.println("Scanning enemy");
27    }
28}
29
30@AllArgsConstructor
31class SpaceShipAdapter implements Ship {
32    AlienCraft ship;
33
34    @Override
35    public void scan() {
36        ship.jorarghugon();
37    }
38
39    @Override
40    public void fire() {
41        ship.drakarys();
42    }
43
44}

Output:

1Scanning enemy in sector NORTH
2Firing weapon at sector NORTH

UML Diagram Adapter design pattern.

2. Composite Pattern

Composite pattern is used when we have to represent a part-whole hierarchy.A group of objects should behave in a similar way,tree like structure. Here we have a playlist which can contain songs or other playlist and those playlist can have songs of their own.

 1package com.demo.project62.composite;
 2
 3import java.util.ArrayList;
 4import java.util.List;
 5
 6import lombok.AllArgsConstructor;
 7import lombok.RequiredArgsConstructor;
 8
 9//When the group of objects should behave as the single object
10public class Main {
11
12    public static void main(String[] args) {
13        SongComponent playlist1 = new PlayList("playlist_1");
14        SongComponent playlist2 = new PlayList("playlist_2");
15        SongComponent playlist3 = new PlayList("playlist_3");
16        SongComponent myplaylist = new PlayList("myplaylist");
17        myplaylist.add(playlist1);
18        myplaylist.add(playlist2);
19        playlist1.add(new Song("Song1"));
20        playlist1.add(new Song("Song2"));
21        playlist1.add(playlist3);
22        playlist3.add(new Song("Song3"));
23
24        myplaylist.displaySongInfo();
25    }
26}
27
28abstract class SongComponent {
29
30    public void add(SongComponent c) {
31        throw new UnsupportedOperationException();
32    }
33
34    public String getSong() {
35        throw new UnsupportedOperationException();
36    }
37
38    public void displaySongInfo() {
39        throw new UnsupportedOperationException();
40    }
41}
42
43@RequiredArgsConstructor
44class PlayList extends SongComponent {
45
46    final String playListName;
47    List<SongComponent> componentLst = new ArrayList<>();
48
49    @Override
50    public void add(SongComponent c) {
51        componentLst.add(c);
52    }
53
54    @Override
55    public void displaySongInfo() {
56        System.out.println("Playlist Name: " + playListName);
57        for (SongComponent s : componentLst) {
58            s.displaySongInfo();
59        }
60    }
61}
62
63@AllArgsConstructor
64class Song extends SongComponent {
65    String songName;
66
67    @Override
68    public String getSong() {
69        return songName;
70    }
71
72    @Override
73    public void displaySongInfo() {
74        System.out.println("Song: " + songName);
75    }
76}

Output:

1Playlist Name: myplaylist
2Playlist Name: playlist_1
3Song: Song1
4Song: Song2
5Playlist Name: playlist_3
6Song: Song3
7Playlist Name: playlist_2

3. Proxy Pattern

Proxy pattern is used when we want to provide controlled access of a functionality. A real world example would be when a lawyer restricts the questions police would ask a mob boss. You can add only one proxy per class.

 1package com.demo.project62.proxy;
 2
 3interface Command {
 4    public void runCommand(String cmd);
 5}
 6
 7public class Main {
 8
 9    public static void main(String[] args) {
10        Proxy proxy = new Proxy();
11        proxy.runCommand("rm");
12        proxy.runCommand("dir");
13    }
14}
15
16class CommandImpl implements Command {
17
18    @Override
19    public void runCommand(String cmd) {
20        System.out.println("Running : " + cmd);
21    }
22}
23
24class Proxy implements Command {
25
26    Command cmdObj;
27
28    public Proxy() {
29        this.cmdObj = new CommandImpl();
30    }
31
32    @Override
33    public void runCommand(String cmd) {
34        if (cmd.contains("rm")) {
35            System.out.println("Cant run rm");
36        } else {
37            cmdObj.runCommand(cmd);
38        }
39    }
40
41}

Output:

1Cant run rm
2Running : dir

A much more generic way to doing this using default java class InvocationHandler is shown below.

 1package com.demo.project62.proxy.other;
 2
 3import java.lang.reflect.InvocationHandler;
 4import java.lang.reflect.InvocationTargetException;
 5import java.lang.reflect.Method;
 6
 7interface Command {
 8    public void runCommand(String cmd);
 9}
10
11public class Main {
12
13    public static void main(String[] args) {
14        Command cmd = (Command) CommandProxy.newInstance(new CommandImpl());
15        cmd.runCommand("ls");
16        cmd.runCommand("rm");
17    }
18
19}
20
21class CommandImpl implements Command {
22
23    @Override
24    public void runCommand(String cmd) {
25        System.out.println("Running : " + cmd);
26    }
27}
28
29class CommandProxy implements InvocationHandler {
30    private Object obj;
31
32    private CommandProxy(Object obj) {
33        this.obj = obj;
34    }
35
36    public static Object newInstance(Object obj) {
37        return java.lang.reflect.Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(),
38                new CommandProxy(obj));
39    }
40
41    @Override
42    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
43        Object result;
44        try {
45            if (args[0].equals("rm")) {
46                throw new IllegalAccessException("rm command not allowed");
47            } else {
48                result = method.invoke(obj, args);
49            }
50            return result;
51        } catch (InvocationTargetException ex) {
52            throw ex.getTargetException();
53        } catch (Exception ex) {
54            throw new RuntimeException("invocation exception " + ex.getMessage());
55        }
56    }
57
58}

4. Flyweight Pattern

Flyweight pattern is used when we need to create a lot of Objects of a class eg 100,000 objects. Reduce cost of storage for large objects by sharing. When we share objects we need to determine what is intrinsic and extrinsic attributes. Here beeType is an intrinsic state and will be shared by all bees. The (x,y) coordinates are the extrinsic properties which will vary for each object. Notice that a factory pattern is also seen in the flyweight example below.

 1package com.demo.project62.flyweight;
 2
 3import java.util.HashMap;
 4import java.util.Random;
 5import java.util.concurrent.TimeUnit;
 6
 7import lombok.SneakyThrows;
 8
 9//divide Object property into intrinsic and extrinsic properties
10enum BeeType {
11    WORKER, ATTACKER;
12    public static BeeType getRandom() {
13        return BeeType.values()[new Random().nextInt(2)];
14    }
15}
16
17interface Bee {
18    public void carryOutMission(int x, int y);
19}
20
21public class Main {
22
23    public static void main(String[] args) {
24        for (int i = 0; i < 100000; i++) {
25            int posx = new Random().nextInt(10);
26            int posy = new Random().nextInt(10);
27            FlyweightBeeFactory.getBeeType(BeeType.getRandom()).carryOutMission(posx, posy);
28        }
29        System.out.println("Total Bee objects created:" + FlyweightBeeFactory.bees.size());
30    }
31}
32
33class WorkerBee implements Bee {
34
35    @SneakyThrows
36    public WorkerBee() {
37        //Takes long time
38        System.out.println("Creating worker bee!");
39        TimeUnit.SECONDS.sleep(1);
40    }
41
42    @Override
43    public void carryOutMission(int x, int y) {
44        System.out.println("Depositing honey at (" + x + "," + y + ") quadrant!");
45    }
46
47}
48
49class AttackBee implements Bee {
50
51    @SneakyThrows
52    public AttackBee() {
53        //Takes long time
54        System.out.println("Creating attack bee!");
55        TimeUnit.SECONDS.sleep(1);
56    }
57
58    @Override
59    public void carryOutMission(int x, int y) {
60        System.out.println("Defending (" + x + "," + y + ") quadrant!");
61    }
62
63}
64
65class FlyweightBeeFactory {
66
67    public static final HashMap<BeeType, Bee> bees = new HashMap<>();
68
69    public static Bee getBeeType(BeeType beeType) {
70        Bee bee = bees.get(beeType);
71        if (bee == null) {
72            if (beeType.equals(BeeType.WORKER)) {
73                bee = new WorkerBee();
74            } else {
75                bee = new AttackBee();
76            }
77            bees.put(beeType, bee);
78        }
79        return bee;
80    }
81
82}

Output:

1...
2...
3WORKER_LEADER, Depositing honey at (9,50) quadrant!
4ATTACKER, Depositing honey at (75,68) quadrant!
5WORKER_LEADER, Depositing honey at (25,78) quadrant!
6Total Bee objects created:4

Now lets look at how the bad design would have looked, Here we end up creating large number of objects there by wasting memory. In the solution above we have moved out the extrinsic properties from the Bee class so that we can share the objects.

Bad Design Alert!

 1package com.demo.project62.flyweight.bad;
 2
 3import java.util.Random;
 4import java.util.concurrent.TimeUnit;
 5
 6import lombok.AllArgsConstructor;
 7import lombok.SneakyThrows;
 8
 9enum BeeType {
10    WORKER, ATTACKER;
11    public static BeeType getRandom() {
12        //Returns random bee types.
13        return BeeType.values()[new Random().nextInt(2)];
14    }
15}
16
17interface Bee {
18    public void carryOutMission(int x, int y);
19}
20
21public class Main {
22
23    public static void main(String[] args) {
24        int i = 0;
25        for (; i < 100000; i++) {
26            int posx =  new Random().nextInt(10);
27            int posy =  new Random().nextInt(10);
28            BeeType type = BeeType.getRandom();
29            if(type.equals(BeeType.WORKER)) {
30                new WorkerBee(BeeType.getRandom()).carryOutMission(posx,posy);
31            } else {
32                new AttackBee(BeeType.getRandom()).carryOutMission(posx,posy);
33            }
34
35        }
36        System.out.println("Total Bee objects created:" + i);
37    }
38}
39
40class WorkerBee implements Bee {
41
42    BeeType beeType;
43
44    @SneakyThrows
45    public WorkerBee(BeeType beeType) {
46        //Takes long time
47        TimeUnit.SECONDS.sleep(1);
48        this.beeType = beeType;
49    }
50
51    @Override
52    public void carryOutMission(int x, int y) {
53        System.out.println(beeType + ", Depositing honey at (" + x + "," + y + ") quadrant!");
54    }
55
56}
57
58class AttackBee implements Bee {
59
60    BeeType beeType;
61
62    @SneakyThrows
63    public AttackBee(BeeType beeType) {
64        //Takes long time
65        TimeUnit.SECONDS.sleep(1);
66        this.beeType = beeType;
67    }
68
69    @Override
70    public void carryOutMission(int x, int y) {
71        System.out.println(beeType + ", Defending (" + x + "," + y + ") quadrant!");
72    }
73
74}

Output:

1...
2...
3WORKER_LEADER, Depositing honey at (77,41) quadrant!
4ATTACKER_LEADER, Depositing honey at (54,35) quadrant!
5WORKER, Depositing honey at (7,17) quadrant!
6Total Bee objects created:100000

5. Facade Pattern

Facade pattern is used to give unified interface to a set of interfaces in a subsystem.

 1package com.demo.project62.facade;
 2
 3//makes the subsystem easier to use
 4enum DbType {
 5    ORACLE, MYSQL;
 6}
 7
 8public class Main {
 9    public static void main(String[] args) {
10        HelperFacade.generateReport(DbType.ORACLE);
11        HelperFacade.generateReport(DbType.MYSQL);
12    }
13}
14
15class MysqlHelper {
16
17    public void mysqlReport() {
18        System.out.println("Generating report in mysql");
19    }
20}
21
22class OracleHelper {
23
24    public void oracleReport() {
25        System.out.println("Generating report in oracle");
26    }
27
28}
29
30class HelperFacade {
31
32    public static void generateReport(DbType db) {
33
34        switch (db) {
35            case ORACLE:
36                OracleHelper ohelper = new OracleHelper();
37                ohelper.oracleReport();
38                break;
39            case MYSQL:
40                MysqlHelper mhelper = new MysqlHelper();
41                mhelper.mysqlReport();
42                break;
43        }
44
45    }
46}

Output:

1Generating report in oracle
2Generating report in mysql

6. Bridge Pattern

Bridge Pattern is used to decouple the interfaces from implementation. Prefer Composition over inheritance. There are interface hierarchies in both interfaces as well a implementations.

By decoupling the switch & electric device from each other each can vary independently. You can add new switches, you can add new electric devices independently without increasing complexity.

 1package com.demo.project62.bridge;
 2
 3import lombok.AllArgsConstructor;
 4
 5//Decouple an abstraction from its implementation so that the two can vary independently
 6interface Color {
 7    public void applyColor();
 8}
 9
10public class Main {
11
12    public static void main(String[] args) {
13        Shape tri = new Triangle(new RedColor());
14        tri.applyColor();
15        Shape sqr = new Square(new GreenColor());
16        sqr.applyColor();
17    }
18
19}
20
21class GreenColor implements Color {
22    @Override
23    public void applyColor() {
24        System.out.println("green!");
25    }
26}
27
28class RedColor implements Color {
29    public void applyColor() {
30        System.out.println("red!");
31    }
32}
33
34@AllArgsConstructor
35abstract class Shape {
36
37    protected Color color;
38
39    public abstract void applyColor();
40}
41
42class Square extends Shape {
43
44    public Square(Color c) {
45        super(c);
46    }
47
48    @Override
49    public void applyColor() {
50        System.out.print("Square filled with color ");
51        color.applyColor();
52    }
53}
54
55class Triangle extends Shape {
56
57    public Triangle(Color c) {
58        super(c);
59    }
60
61    @Override
62    public void applyColor() {
63        System.out.print("Triangle filled with color ");
64        color.applyColor();
65    }
66}

Output:

1Pulled Switch, Now turning on :Light!
2----------------
3Pressed Switch, Now turning on :Fan!

UML of Bridge Pattern. There is a bridge between Switch class and ElectricDevice class.

Bad Design Alert!

Lets look at how a problematic code looks like and its eligibility for bridge pattern. In the below code trying to add a new Electric Device + Switch combination is a pain which is solved by the bridge pattern mentioned above.

 1package com.demo.project62.bridge.badway;
 2
 3public class Main {
 4
 5    public static void main(String[] args) {
 6        PullSwitch switch1 = new PullSwitchFan();
 7        PressSwitch switch2 = new PressSwitchLight();
 8        switch1.toggle();
 9        switch2.toggle();
10    }
11}
12
13abstract class Switch {
14    abstract public void toggle();
15}
16
17abstract class PullSwitch extends Switch {
18}
19
20abstract class PressSwitch extends Switch {
21}
22
23class PullSwitchFan extends PullSwitch {
24
25    boolean state;
26
27    @Override
28    public void toggle() {
29        if (state) {
30            System.out.println("Pulled Switch, Now turning off fan");
31            state = Boolean.FALSE;
32        } else {
33            System.out.println("Pulled Switch, Now turning on fan");
34            state = Boolean.TRUE;
35        }
36    }
37}
38
39class PullSwitchLight extends PullSwitch {
40
41    boolean state;
42
43    @Override
44    public void toggle() {
45        if (state) {
46            System.out.println("Pulled Switch, Now turning off light");
47            state = Boolean.FALSE;
48        } else {
49            System.out.println("Pulled Switch, Now turning on light");
50            state = Boolean.TRUE;
51        }
52    }
53}
54
55class PressSwitchFan extends PressSwitch {
56
57    boolean state;
58
59    @Override
60    public void toggle() {
61        if (state) {
62            System.out.println("Pressed Switch, Now turning off fan");
63            state = Boolean.FALSE;
64        } else {
65            System.out.println("Pressed Switch, Now turning on fan");
66            state = Boolean.TRUE;
67        }
68    }
69}
70
71class PressSwitchLight extends PressSwitch {
72
73    boolean state;
74
75    @Override
76    public void toggle() {
77        if (state) {
78            System.out.println("Pressed Switch, Now turning off light");
79            state = Boolean.FALSE;
80        } else {
81            System.out.println("Pressed Switch, Now turning on light");
82            state = Boolean.TRUE;
83        }
84    }
85}

Output:

1Pulled Switch, Now turning on fan
2Pressed Switch, Now turning on light

UML Diagram of problematic code, you can see that heirarchy exists.

7. Decorator Pattern

Decorator design pattern is used to add the functionality by wrapping another class around the core class without modifying the core class. Disadvantage of decorator pattern is that it uses a lot of similar kind of objects.

 1package com.demo.project62.decorator;
 2
 3import java.math.BigDecimal;
 4
 5import lombok.AllArgsConstructor;
 6
 7public class Main {
 8
 9    public static void main(String[] args) {
10        Pizza pizza = new ThickCrustPizza();
11        System.out.println("Pizza: " + pizza.getDescription());
12        System.out.println("Cost: " + pizza.getCost());
13
14        Cheese cheese = new Cheese(pizza);
15        System.out.println("Pizza: " + cheese.getDescription());
16        System.out.println("Cost: " + cheese.getCost());
17
18        Cheese doubleCheese = new Cheese(cheese);
19        System.out.println("Pizza: " + doubleCheese.getDescription());
20        System.out.println("Cost: " + doubleCheese.getCost());
21    }
22
23}
24
25interface Pizza {
26    public String getDescription();
27    public BigDecimal getCost();
28}
29
30class ThickCrustPizza implements Pizza {
31
32    @Override
33    public String getDescription() {
34        return "Thick Crust Pizza";
35    }
36
37    @Override
38    public BigDecimal getCost() {
39        return new BigDecimal(10.00);
40    }
41}
42
43@AllArgsConstructor
44class PizzaToppingDecorator implements Pizza {
45
46    Pizza pizza;
47
48    @Override
49    public String getDescription() {
50        return pizza.getDescription();
51    }
52
53    @Override
54    public BigDecimal getCost() {
55        return pizza.getCost();
56    }
57}
58
59class Cheese extends PizzaToppingDecorator {
60
61    public Cheese(Pizza pizza) {
62        super(pizza);
63    }
64
65    @Override
66    public BigDecimal getCost() {
67        return (new BigDecimal(2.00).add(pizza.getCost()));
68    }
69
70    @Override
71    public String getDescription() {
72        return pizza.getDescription() + " + Cheese";
73    }
74}

Output:

1Pizza: Thick Crust Pizza
2Cost: 10
3Pizza: Thick Crust Pizza + Cheese
4Cost: 12
5Pizza: Thick Crust Pizza + Cheese + Cheese
6Cost: 14

UML of Decorator Pattern

Behavioral Design Patterns

Behavioral patterns help design classes with better interaction between objects and provide lose coupling.

1. Template Pattern

Template Pattern used to create a method stub and deferring some of the steps of implementation to the subclasses. Template method defines the steps to execute an algorithm and it can provide default implementation that might be common for all or some of the subclasses.

 1package com.demo.project62.template;
 2
 3public class Main {
 4
 5    public static void main(String[] args) {
 6        HouseTemplate houseType = new WoodenHouse();
 7        houseType.buildHouse();
 8        System.out.println("-------------------------");
 9        houseType = new GlassHouse();
10        houseType.buildHouse();
11    }
12}
13
14class GlassHouse extends HouseTemplate {
15
16    @Override
17    public void buildWalls() {
18        System.out.println("Building Glass Walls");
19    }
20
21    @Override
22    public void buildPillars() {
23        System.out.println("Building Pillars with glass coating");
24    }
25}
26
27class WoodenHouse extends HouseTemplate {
28
29    @Override
30    public void buildWalls() {
31        System.out.println("Building Wooden Walls");
32    }
33
34    @Override
35    public void buildPillars() {
36        System.out.println("Building Pillars with Wood coating");
37    }
38
39}
40
41
42abstract class HouseTemplate {
43
44    //template method, final so subclasses can't override
45    public final void buildHouse() {
46        buildFoundation();
47        buildPillars();
48        buildWalls();
49        buildWindows();
50        System.out.println("House is built.");
51    }
52
53    //default implementation
54    private void buildWindows() {
55        System.out.println("Building Glass Windows");
56    }
57
58    //methods to be implemented by subclasses
59    public abstract void buildWalls();
60
61    public abstract void buildPillars();
62
63    private void buildFoundation() {
64        System.out.println("Building foundation with cement,iron rodsand sand");
65    }
66}

Output:

 1Building foundation with cement,iron rodsand sand
 2Building Pillars with Wood coating
 3Building Wooden Walls
 4Building Glass Windows
 5House is built.
 6-------------------------
 7Building foundation with cement,iron rodsand sand
 8Building Pillars with glass coating
 9Building Glass Walls
10Building Glass Windows
11House is built.

2. Mediator Pattern

Mediator pattern is used to provide a centralized communication medium between different objects.

 1package com.demo.project62.mediator;
 2
 3import java.util.ArrayList;
 4import java.util.List;
 5
 6import lombok.AllArgsConstructor;
 7
 8interface ChatMediator {
 9
10    public void sendMessage(String msg, User user);
11
12    void addUser(User user);
13}
14
15public class Main {
16
17    public static void main(String[] args) {
18        ChatMediator mediator = new ChatMediatorImpl();
19        User user1 = new User(mediator, "Raj");
20        User user2 = new User(mediator, "Jacob");
21        User user3 = new User(mediator, "Henry");
22        User user4 = new User(mediator, "Stan");
23        mediator.addUser(user1);
24        mediator.addUser(user2);
25        mediator.addUser(user3);
26        mediator.addUser(user4);
27        user1.send("Hi All");
28
29    }
30}
31
32class ChatMediatorImpl implements ChatMediator {
33
34    private List<User> users = new ArrayList<>();
35
36    @Override
37    public void addUser(User user) {
38        this.users.add(user);
39    }
40
41    @Override
42    public void sendMessage(String msg, User user) {
43        for (User u : this.users) {
44            if (u != user) {
45                u.receive(msg);
46            }
47        }
48    }
49}
50
51@AllArgsConstructor
52class User {
53
54    private ChatMediator mediator;
55    private String name;
56
57    public void send(String msg) {
58        System.out.println(this.name + ": Sending Message=" + msg);
59        mediator.sendMessage(msg, this);
60    }
61
62    public void receive(String msg) {
63        System.out.println(this.name + ": Received Message:" + msg);
64    }
65}

Output:

1Raj: Sending Message=Hi All
2Jacob: Received Message:Hi All
3Henry: Received Message:Hi All
4Stan: Received Message:Hi All

3. Chain of Responsibility Pattern

Chain of responsibility pattern is used when a request from client is passed to a chain of objects to process them.

  1package com.demo.project62.chainofresponsibility;
  2
  3interface DispenseChain {
  4
  5    void setNextChain(DispenseChain nextChain);
  6
  7    void dispense(int amount);
  8}
  9
 10public class Main {
 11
 12    public static void main(String[] args) {
 13        ATMDispenseChain atmDispenser = new ATMDispenseChain();
 14        int amount = 530;
 15        if (amount % 10 != 0) {
 16            System.out.println("Amount should be in multiple of10s.");
 17        } else {
 18            atmDispenser.c1.dispense(amount);
 19        }
 20    }
 21}
 22
 23class ATMDispenseChain {
 24
 25    public DispenseChain c1;
 26
 27    public ATMDispenseChain() {
 28
 29        DispenseChain c1 = new Dollar50Dispenser();
 30        DispenseChain c2 = new Dollar20Dispenser();
 31        DispenseChain c3 = new Dollar10Dispenser();
 32
 33        this.c1 = c1;
 34        c1.setNextChain(c2);
 35        c2.setNextChain(c3);
 36    }
 37
 38}
 39
 40
 41class Dollar10Dispenser implements DispenseChain {
 42
 43    private DispenseChain chain;
 44
 45    @Override
 46    public void setNextChain(DispenseChain nextChain) {
 47        this.chain = nextChain;
 48    }
 49
 50    @Override
 51    public void dispense(int amount) {
 52        if (amount >= 10) {
 53            int num = amount / 10;
 54            int remainder = amount % 10;
 55            System.out.println("Dispensing " + num + " 10$ note");
 56            if (remainder != 0) {
 57                this.chain.dispense(remainder);
 58            }
 59        } else {
 60            this.chain.dispense(amount);
 61        }
 62    }
 63}
 64
 65class Dollar20Dispenser implements DispenseChain {
 66
 67    private DispenseChain chain;
 68
 69    @Override
 70    public void setNextChain(DispenseChain nextChain) {
 71        this.chain = nextChain;
 72    }
 73
 74    @Override
 75    public void dispense(int amount) {
 76        if (amount >= 20) {
 77            int num = amount / 20;
 78            int remainder = amount % 20;
 79            System.out.println("Dispensing " + num + " 20$ note");
 80            if (remainder != 0) {
 81                this.chain.dispense(remainder);
 82            }
 83        } else {
 84            this.chain.dispense(amount);
 85        }
 86    }
 87}
 88
 89class Dollar50Dispenser implements DispenseChain {
 90
 91    private DispenseChain chain;
 92
 93    @Override
 94    public void setNextChain(DispenseChain nextChain) {
 95        this.chain = nextChain;
 96    }
 97
 98    @Override
 99    public void dispense(int amount) {
100        if (amount >= 50) {
101            int num = amount / 50;
102            int remainder = amount % 50;
103            System.out.println("Dispensing " + num + " 50$ note");
104            if (remainder != 0) {
105                this.chain.dispense(remainder);
106            }
107        } else {
108            this.chain.dispense(amount);
109        }
110    }
111}

Output:

1Dispensing 10 50$ note
2Dispensing 1 20$ note
3Dispensing 1 10$ note

4. Observer Pattern

Observer design pattern is used when we want to get notified about state changes of a object. An Observer watches the Subject here and any changes on Subject are notified to the Observer.

 1package com.demo.project62.observer;
 2
 3import java.util.ArrayList;
 4import java.util.List;
 5
 6interface Observer {
 7    public void notify(String tick);
 8}
 9
10interface Subject {
11    public void registerObserver(Observer observer);
12
13    public void notifyObservers(String tick);
14}
15
16public class Main {
17
18    public static void main(String[] args) {
19        Feed feed = new Feed();
20        feed.registerObserver(new AppleStockObserver());
21        feed.registerObserver(new GoogleStockObserver());
22        feed.notifyObservers("APPL: 162.33");
23        feed.notifyObservers("GOOGL: 1031.22");
24    }
25}
26
27class AppleStockObserver implements Observer {
28    @Override
29    public void notify(String tick) {
30        if (tick != null && tick.contains("APPL")) {
31            System.out.println("Apple Stock Price: " + tick);
32        }
33    }
34}
35
36class GoogleStockObserver implements Observer {
37    @Override
38    public void notify(String tick) {
39        if (tick != null && tick.contains("GOOGL")) {
40            System.out.println("Google Stock Price: " + tick);
41        }
42    }
43}
44
45class Feed implements Subject {
46    List<Observer> observerLst = new ArrayList<>();
47
48    @Override
49    public void registerObserver(Observer observer) {
50        observerLst.add(observer);
51    }
52
53    @Override
54    public void notifyObservers(String tick) {
55        observerLst.forEach(e -> e.notify(tick));
56    }
57}

Output:

1Apple Stock Price: APPL: 162.33
2Google Stock Price: GOOGL: 1031.22

5. Strategy Pattern

Strategy pattern is used when we have multiple algorithm for a specific task and client decides the actual implementation to be used at runtime. This is also known as Policy Pattern.

 1package com.demo.project62.stategy;
 2
 3interface PaymentStrategy {
 4
 5    void pay(int amount);
 6}
 7
 8public class Main {
 9
10    public static void main(String[] args) {
11        new ShoppingCart().pay(new CreditCardStrategy(), 10);
12        new ShoppingCart().pay(new PaypalStrategy(), 10);
13    }
14}
15
16class CreditCardStrategy implements PaymentStrategy {
17
18    @Override
19    public void pay(int amount) {
20        System.out.println("Paid by credit card: " + amount);
21    }
22
23}
24
25class PaypalStrategy implements PaymentStrategy {
26
27    @Override
28    public void pay(int amount) {
29        System.out.println("Paid by paypal: " + amount);
30    }
31
32}
33
34class ShoppingCart {
35
36    public void pay(PaymentStrategy paymentMethod, Integer amount) {
37        paymentMethod.pay(amount);
38    }
39}

Output:

1Paid by creditcard: 10
2Paid by paypall: 10

6. Command Pattern

Command pattern is used when request is wrapped and passed to invoker which then inturn invokes the encapsulated command. Here Command is our command interface, Stock class is our request. BuyStock and SellStock implementing Order interface which does the actual command processing.

 1package com.demo.project62.command;
 2
 3import java.util.ArrayList;
 4import java.util.List;
 5
 6import lombok.AllArgsConstructor;
 7
 8interface Command {
 9    void execute();
10}
11
12public class Main {
13
14    public static void main(String[] args) {
15
16        Stock stock1 = new Stock("GOOGL", 10);
17        Stock stock2 = new Stock("IBM", 20);
18
19        BuyStock buyStockCmd = new BuyStock(stock1);
20        SellStock sellStockCmd = new SellStock(stock2);
21
22        Broker broker = new Broker();
23        broker.takeOrder(buyStockCmd);
24        broker.takeOrder(sellStockCmd);
25
26        broker.placeOrders();
27
28    }
29}
30
31@AllArgsConstructor
32class Stock {
33
34    private String name;
35    private int quantity;
36
37    public void buy() {
38        System.out.println("Stock [ Name: " + name + ", Quantity: " + quantity + " ] bought");
39    }
40
41    public void sell() {
42        System.out.println("Stock [ Name: " + name + ", Quantity: " + quantity + " ] sold");
43    }
44}
45
46@AllArgsConstructor
47class BuyStock implements Command {
48    private Stock stock;
49
50    public void execute() {
51        stock.buy();
52    }
53}
54
55@AllArgsConstructor
56class SellStock implements Command {
57    private Stock stock;
58
59    public void execute() {
60        stock.sell();
61    }
62}
63
64class Broker {
65    private List<Command> cmdLst = new ArrayList<Command>();
66
67    public void takeOrder(Command cmd) {
68        cmdLst.add(cmd);
69    }
70
71    public void placeOrders() {
72        for (Command cmd : cmdLst) {
73            cmd.execute();
74        }
75        cmdLst.clear();
76    }
77}

Output:

1Stock [ Name: GOOGL, Quantity: 10 ] bought
2Stock [ Name: IBM, Quantity: 20 ] sold

7. State Pattern

State pattern is used when object changes its behaviour based on internal state. You avoid writing the conditional if-else logic to determine the type of action to be taken based on state of object. Notice that GameContext also implements State along with StartState,StopState classes.

 1package com.demo.project62.state;
 2
 3import lombok.AllArgsConstructor;
 4import lombok.Data;
 5import lombok.NoArgsConstructor;
 6
 7interface State {
 8    public void doAction();
 9}
10
11public class Main {
12    public static void main(String[] args) {
13        GameContext game = new GameContext();
14
15        StartState startState = new StartState();
16        StopState stopState = new StopState();
17
18        game.setState(startState);
19        game.doAction();
20
21        game.setState(stopState);
22        game.doAction();
23    }
24
25}
26
27class StartState implements State {
28
29    public void doAction() {
30        System.out.println("Roll the dice!");
31    }
32}
33
34class StopState implements State {
35
36    public void doAction() {
37        System.out.println("Game Over!");
38    }
39}
40
41@AllArgsConstructor
42@NoArgsConstructor
43@Data
44class GameContext implements State {
45    private State state;
46
47    @Override
48    public void doAction() {
49        this.state.doAction();
50    }
51}

Output:

1Roll the dice!
2Game Over!

8. Visitor Pattern

Visitor pattern is used to add methods to different types of classes without altering those classes. Here we have moved the tax calculation outside each item.

 1package com.demo.project62.visitor;
 2
 3import lombok.AllArgsConstructor;
 4import lombok.Data;
 5
 6interface Visitable {
 7    public double accept(Visitor visitor);
 8}
 9
10interface Visitor {
11    public double visit(Liquor item);
12
13    public double visit(Grocery item);
14}
15
16public class Main {
17    public static void main(String[] args) {
18
19        Visitor taxCalculator = new TaxVisitor();
20        Liquor liquor = new Liquor("Black Dog", 12.00d);
21        System.out.println("Price of liquor: " + liquor.accept(taxCalculator));
22
23        Grocery grocery = new Grocery("Potato Chips", 12.00d);
24        System.out.println("Price of grocery: " + grocery.accept(taxCalculator));
25
26    }
27}
28
29@AllArgsConstructor
30@Data
31class Liquor implements Visitable {
32    String name;
33    double price;
34
35    @Override
36    public double accept(Visitor visitor) {
37        return visitor.visit(this);
38    }
39}
40
41@AllArgsConstructor
42@Data
43class Grocery implements Visitable {
44    String name;
45    double price;
46
47    @Override
48    public double accept(Visitor visitor) {
49        return visitor.visit(this);
50    }
51}
52
53class TaxVisitor implements Visitor {
54
55    @Override
56    public double visit(Liquor item) {
57        return item.price * .30 + item.price;
58    }
59
60    @Override
61    public double visit(Grocery item) {
62        return item.price * .10 + item.price;
63    }
64}

Output:

1Price of liquor: 15.6
2Price of grocery: 13.2

9. Interpreter Pattern

Interpreter pattern provides a way to evaluate language grammar or expression.

 1package com.demo.project62.interpreter;
 2
 3import lombok.AllArgsConstructor;
 4import lombok.Data;
 5
 6interface Expression {
 7    String interpret(InterpreterContext ctx);
 8}
 9
10public class Main {
11    public static void main(String[] args) {
12        String input = "30 in binary";
13        if (input.contains("binary")) {
14            int val = Integer.parseInt(input.substring(0, input.indexOf(" ")));
15            System.out.println(new IntToBinaryExpression(val).interpret(new InterpreterContext()));
16        }
17
18        input = "30 in hexadecimal";
19        if (input.contains("hexadecimal")) {
20            int val = Integer.parseInt(input.substring(0, input.indexOf(" ")));
21            System.out.println(new IntToHexExpression(val).interpret(new InterpreterContext()));
22        }
23    }
24
25}
26
27class InterpreterContext {
28    public String getBinaryFormat(int val) {
29        return Integer.toBinaryString(val);
30    }
31
32    public String getHexFormat(int val) {
33        return Integer.toHexString(val);
34    }
35}
36
37@Data
38@AllArgsConstructor
39class IntToBinaryExpression implements Expression {
40
41    int val;
42
43    @Override
44    public String interpret(InterpreterContext ctx) {
45        return ctx.getBinaryFormat(val);
46    }
47}
48
49@Data
50@AllArgsConstructor
51class IntToHexExpression implements Expression {
52
53    int val;
54
55    @Override
56    public String interpret(InterpreterContext ctx) {
57        return ctx.getHexFormat(val);
58    }
59}

Output:

111110
21e

10. Iterator Pattern

Iterator pattern is used to provide standard way to traverse through group of objects. In the example below we provide 2 types of iterators over the fruit collection, we could have let the user write his own iterator but if there are many clients using the iterator then it would be difficult to maintain. Notice that FruitIterator is private and inner class, this hides the implementation details from the client. Logic of iteration is internal to the collection.

 1package com.demo.project62.iterator;
 2
 3import java.util.ArrayList;
 4import java.util.Collections;
 5import java.util.Comparator;
 6import java.util.Iterator;
 7import java.util.List;
 8
 9import lombok.AllArgsConstructor;
10import lombok.Data;
11
12interface FruitCollection {
13    public Iterator getIterator(String type);
14}
15
16public class Main {
17    public static void main(String[] args) {
18
19        FruitCollectionImpl collection = new FruitCollectionImpl();
20
21        for (Iterator iter = collection.getIterator("COLOR"); iter.hasNext(); ) {
22            Fruit fruit = (Fruit) iter.next();
23            System.out.println(fruit);
24        }
25        System.out.println("-------------------------------");
26        for (Iterator iter = collection.getIterator("TYPE"); iter.hasNext(); ) {
27            Fruit fruit = (Fruit) iter.next();
28            System.out.println(fruit);
29        }
30    }
31}
32
33@AllArgsConstructor
34@Data
35class Fruit {
36    String type;
37    String color;
38}
39
40class FruitCollectionImpl implements FruitCollection {
41
42    List<Fruit> fruits;
43
44    FruitCollectionImpl() {
45        fruits = new ArrayList<>();
46        fruits.add(new Fruit("Banana", "Green"));
47        fruits.add(new Fruit("Apple", "Green"));
48        fruits.add(new Fruit("Banana", "Yellow"));
49        fruits.add(new Fruit("Cherry", "Red"));
50        fruits.add(new Fruit("Apple", "Red"));
51    }
52
53    @Override
54    public Iterator getIterator(String type) {
55        if (type.equals("COLOR")) {
56            return new FruitIterator("COLOR");
57        } else {
58            return new FruitIterator("TYPE");
59        }
60    }
61
62    private class FruitIterator implements Iterator {
63        int index;
64        List<Fruit> sortedFruits = new ArrayList<>(fruits);
65
66        FruitIterator(String iteratorType) {
67            if (iteratorType.equals("COLOR")) {
68                Collections.sort(sortedFruits, Comparator.comparing(Fruit::getColor));
69            } else {
70                Collections.sort(sortedFruits, Comparator.comparing(Fruit::getType));
71            }
72        }
73
74        @Override
75        public boolean hasNext() {
76            if (index < sortedFruits.size()) {
77                return true;
78            }
79            return false;
80        }
81
82        @Override
83        public Object next() {
84            if (this.hasNext()) {
85                return sortedFruits.get(index++);
86            }
87            return null;
88        }
89    }
90
91}

Output:

 1Fruit(type=Banana, color=Green)
 2Fruit(type=Apple, color=Green)
 3Fruit(type=Cherry, color=Red)
 4Fruit(type=Apple, color=Red)
 5Fruit(type=Banana, color=Yellow)
 6-------------------------------
 7Fruit(type=Apple, color=Green)
 8Fruit(type=Apple, color=Red)
 9Fruit(type=Banana, color=Green)
10Fruit(type=Banana, color=Yellow)
11Fruit(type=Cherry, color=Red)

11. Memento Pattern

Memento pattern is used to restore state of an object to a previous state.

Memento pattern involves three classes.

  • Originator: The core class which holds a state. This state will need to be reverted to previous states. Think of this as your text editor text data.
  • Memento: The class has all the same attributes as Originator class and is used to hold values that will be restored back to the Originator class. Think of this as a temporary variable. Each time you click on save a memento is created and added to the list so that it can be reverted later.
  • CareTaker - This class takes ownership of creating and restoring memento.

In the example below you can create a Originator object and change its state many times, only when you call the CareTaker.save method a memento gets created so that an undo operation later on can revert to that state. The list mementoList is private so only caretaker has access to the memento objects ensuring integrity of data. Take special care if the attribute is immutable in the undoState method.

 1package com.demo.project62.memento;
 2
 3import java.util.ArrayList;
 4import java.util.List;
 5
 6import lombok.AllArgsConstructor;
 7import lombok.Data;
 8import lombok.NoArgsConstructor;
 9import lombok.RequiredArgsConstructor;
10
11public class Main {
12
13    public static void main(String[] args) {
14
15        Originator originator = new Originator();
16        CareTaker careTaker = new CareTaker(originator);
17        careTaker.save();
18
19        originator.setState("State #1");
20        originator.setState("State #2");
21        careTaker.save();
22
23        originator.setState("State #3");
24        careTaker.save();
25
26        originator.setState("State #4");
27        System.out.println("Current State: " + originator.getState());
28
29        careTaker.undo();
30        System.out.println("Current State: " + originator.getState());
31
32        careTaker.undo();
33        System.out.println("Current State: " + originator.getState());
34
35        careTaker.undo();
36        careTaker.undo();
37        careTaker.undo();
38        System.out.println("Current State: " + originator.getState());
39    }
40}
41
42@Data
43@AllArgsConstructor
44class Memento {
45    private String state;
46}
47
48@Data
49@AllArgsConstructor
50@NoArgsConstructor
51class Originator {
52    private String state;
53
54    public Memento saveState() {
55        return new Memento(this.state);
56    }
57
58    public void undoState(Memento memento) {
59        this.state = memento.getState();
60    }
61
62}
63
64@RequiredArgsConstructor
65class CareTaker {
66    final Originator origin;
67    private List<Memento> mementoList = new ArrayList<Memento>();
68
69    public void save() {
70        if (origin.getState() != null) {
71            mementoList.add(origin.saveState());
72        }
73    }
74
75    public void undo() {
76        if (!mementoList.isEmpty()) {
77            origin.undoState(mementoList.get(mementoList.size() - 1));
78            mementoList.remove(mementoList.size() - 1);
79        }
80    }
81}

Output:

1Current State: State #4
2Current State: State #3
3Current State: State #2
4Current State: State #2

Differences

1. Difference between bridge pattern and adapter pattern

Bridge pattern is built upfront you break things at design time to make changes so that functionality can be added without tight coupling, adapter pattern works after code is already designed like legacy code.

2. Difference between mediator pattern and observer pattern

In observer, many objects are interested in the state change of one object. They are not interested in each other. So the relation is one to many. In mediator, many objects are interested to communicate many other objects. Here the relation is many to many.

3. Difference between chain of responsibility and command pattern

In chain of responsibility pattern, the request is passed to potential receivers, whereas the command pattern uses a command object that encapsulates a request.

4. Difference between adapter pattern and decorator pattern

Adapter pattern only adapts functionality, decorator adds more functionality.

5. Difference between adapter pattern and facade pattern

Adapter pattern just links two incompatible interfaces. A facade is used when one wants an easier or simpler interface to work with.

comments powered by Disqus