The Chain of Responsibility Pattern is a squence of handlers processing an event one after another or can abort the chain. Think of a car MOT's you go through the checks and if one fails then you can abort, if not then the chain is complete. The chain can be implemented as a chain of references or a centralized construct (see second example).
Chain of Responsibility example | package uk.co.datadisk.Behavioral.chainOfResponsibility; // A chain of components who all get a chance to process a command or a query, // optionally having default processing implementation and an ability to // terminate the process chain // Sequence of handlers processing an event one after another or abort the chain class Creature { public String name; public int attack, defense; public Creature(String name, int attack, int defense) { this.name = name; this.attack = attack; this.defense = defense; } @Override public String toString() { return "Creature{" + "name='" + name + '\'' + ", attack=" + attack + ", defense=" + defense + '}'; } } class CreatureModifier { protected Creature creature; protected CreatureModifier next; public CreatureModifier(Creature creature) { this.creature = creature; } public void add(CreatureModifier cm){ if (next != null){ next.add(cm); } else { next = cm; } } public void handle(){ if (next != null){ next.handle(); } } } class DoubleAttackModifier extends CreatureModifier { public DoubleAttackModifier(Creature creature) { super(creature); } @Override public void handle() { System.out.println("Doubling " + creature.name + "'s attack"); creature.attack *= 2; super.handle(); } } class IncreaseDefenseModifier extends CreatureModifier { public IncreaseDefenseModifier(Creature creature) { super(creature); } @Override public void handle() { System.out.println("Increasing " + creature.name + "'s defense"); creature.defense += 3; super.handle(); } } class NoBonusesModifier extends CreatureModifier { public NoBonusesModifier(Creature creature) { super(creature); } @Override public void handle() { // do nothing, don't call super.handle() thus breaks the chain System.out.println("No Bonuses for you!!!!"); } } public class Main_COR_1 { public static void main(String[] args) { Creature goblin = new Creature("Goblin", 2, 2); System.out.println(goblin); CreatureModifier root = new CreatureModifier(goblin); root.add(new NoBonusesModifier(goblin)); // breaks the chain of responsibility System.out.println("Let's double goblin's attack..."); root.add(new DoubleAttackModifier(goblin)); // wont get applied System.out.println("Let's increase the goblin's defense"); root.add(new IncreaseDefenseModifier(goblin)); // won't get applied // traverses the chain of responsibility root.handle(); System.out.println(goblin); } } |
Chain of Responsibility + Observer + mediator + memento example | package uk.co.datadisk.Behavioral.chainOfResponsibility; // chain of responsibility + observer + mediator + (dash) memento import java.util.HashMap; import java.util.Map; import java.util.function.Consumer; // command query separation - CQS class Event<Args> { private int index = 0; private Map<Integer, Consumer<Args>> handlers = new HashMap<>(); public int subscribe(Consumer<Args> handler){ int i = index; handlers.put(index++, handler); return i; } public void unsubscribe(int key){ handlers.remove(key); } public void fire(Args args){ for (Consumer<Args> handler : handlers.values()){ System.out.println("Firing handlers : " + handlers.size()); handler.accept(args); } } } class Query { public String creatureName; enum Argument { ATTACK, DEFENSE; } public Argument argument; public int result; public Query(String creatureName, Argument argument, int result) { this.creatureName = creatureName; this.argument = argument; this.result = result; } } // Mediator class Game { public Event<Query> queries = new Event<>(); } class Creature2 { private Game game; public String name; public int baseAttack, baseDefence; public Creature2(Game game, String name, int baseAttack, int baseDefence) { this.game = game; this.name = name; this.baseAttack = baseAttack; this.baseDefence = baseDefence; } public int getAttack() { Query q = new Query(name, Query.Argument.ATTACK, baseAttack); game.queries.fire(q); return q.result; } public int getDefence() { Query q = new Query(name, Query.Argument.DEFENSE, baseDefence); game.queries.fire(q); return q.result; } @Override public String toString() { return "Creature2{" + "name='" + name + '\'' + ", attack=" + getAttack() + ", defence=" + getDefence() + '}'; } } class CreatureModifier2 { protected Game game; protected Creature2 creature2; public CreatureModifier2(Game game, Creature2 creature2) { this.game = game; this.creature2 = creature2; } } class DoubleAttackModifier2 extends CreatureModifier2 implements AutoCloseable { private final int token; public DoubleAttackModifier2(Game game, Creature2 creature2) { super(game, creature2); token = game.queries.subscribe(q -> { if(q.creatureName.equals(creature2.name) && q.argument == Query.Argument.ATTACK){ q.result *= 2; } }); } @Override public void close() /*throws Exception */ { game.queries.unsubscribe(token); } } class IncreaseDefenseModifier2 extends CreatureModifier2 { public IncreaseDefenseModifier2(Game game, Creature2 creature2) { super(game, creature2); game.queries.subscribe(q -> { if(q.creatureName.equals(creature2.name) && q.argument == Query.Argument.DEFENSE){ q.result += 3; } }); } } public class Main_COR_2 { public static void main(String[] args) { Game game = new Game(); Creature2 goblin = new Creature2(game, "Strong Goblin", 2, 2); System.out.println(goblin); IncreaseDefenseModifier2 icm = new IncreaseDefenseModifier2(game, goblin); DoubleAttackModifier2 dam = new DoubleAttackModifier2(game, goblin); try { System.out.println(goblin); dam.close(); } catch (Exception e){ e.printStackTrace(); } System.out.println(goblin); } } |
The Command Pattern uses an Object that represents an operation (action/s) that can be rolled back, can also be used for callbacks. You encapsulate all the details of an operation in a separate object (which could be stored to disk) which you can use to rollback any changes. Recording of history and can be used in callbacks, thread queues/pools - decouples thread from the command object action, undoing actions, recording history, GUI buttons, etc.
Command example | package uk.co.datadisk.Behavioral.command; // Use an Object that represents an operation (action/s) that can be rolled back import java.util.Arrays; import java.util.Collections; import java.util.List; class BankAccount { private int balance; private int overdraftLimit = -500; public void deposit(int amount){ balance += amount; System.out.println("Deposited " + amount + " balance is now " + balance); } public boolean withdraw(int amount){ if(balance - amount >= overdraftLimit){ balance -= amount; System.out.println("Withdrew " + amount + ", balance is now " + balance); return true; } return false; } @Override public String toString() { return "BankAccount{" + "balance=" + balance + '}'; } } interface Command { void call(); void undo(); } class BankAccountCommand implements Command { private BankAccount account; private boolean succeeded; public enum Action { DEPOSIT, WITHDRAW } private Action action; private int amount; public BankAccountCommand(BankAccount account, Action action, int amount) { this.account = account; this.action = action; this.amount = amount; } @Override public void call() { switch (action){ case DEPOSIT: succeeded = true; account.deposit(amount); break; case WITHDRAW: succeeded = account.withdraw(amount); break; } } @Override public void undo() { if(!succeeded) return; switch (action){ case DEPOSIT: account.withdraw(amount); break; case WITHDRAW: account.deposit(amount); break; } } } public class Main_Command_1 { public static void main(String[] args) { BankAccount ba = new BankAccount(); System.out.println(ba); List<BankAccountCommand> commands = Arrays.asList( new BankAccountCommand(ba, BankAccountCommand.Action.DEPOSIT, 100), new BankAccountCommand(ba, BankAccountCommand.Action.WITHDRAW, 1000) ); for (Command c : commands){ c.call(); System.out.println(ba); } System.out.println("-------------------------------"); Collections.reverse(commands); for (Command c : commands){ c.undo(); System.out.println(ba); } } } |
The Intepreter Pattern uses textual input (strings) that need to be processed (turned into OOP structures), compilers, HTML, regular expressions, etc are all intepreters. There are two stages firstly separate lexical tokens (lexing) and then interpreting sequences of said tokens (parsing). A good example is the string "3 + 5 * 4 / 2" and turning this into code and calculating the result.
Intepreter example | package uk.co.datadisk.Behavioral.interpreter; // Textual input (String) needs to be processed (turned into OOP structures) // compilers are interpreters, HTML is also interpreter, regular expressions, etc // There are two stages firstly separate lexical tokens (lexing) and // then interpreting sequences of said tokens (parsing). import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; interface Element { int eval(); } class Integer implements Element { private int value; public Integer(int value) { this.value = value; } @Override public int eval() { return value; } } class BinaryOperation implements Element { public enum Type { ADDITION, SUBTRACTION } public Type type; public Element left, right; @Override public int eval() { switch (type){ case ADDITION: return left.eval() + right.eval(); case SUBTRACTION: return left.eval() - right.eval(); default: return 0; } } } class Token { public enum Type { INTEGER, PLUS, MINUS, LPAREN, RPAREN } public Type type; public String text; public Token(Type type, String text) { this.type = type; this.text = text; } @Override public String toString() { return "`" + text + "`"; } } public class Main_Interpreter_1 { static List<Token> lex(String input) { ArrayList<Token> result = new ArrayList<>(); for (int i = 0; i < input.length(); i++) { switch (input.charAt(i)) { case '+': result.add(new Token(Token.Type.PLUS, "+")); break; case '-': result.add(new Token(Token.Type.MINUS, "-")); break; case '(': result.add(new Token(Token.Type.LPAREN, "(")); break; case ')': result.add(new Token(Token.Type.RPAREN, ")")); break; default: StringBuilder sb = new StringBuilder("" + input.charAt(i)); for (int j = i + 1; j < input.length(); ++j) { if (Character.isDigit(input.charAt(j))) { sb.append(input.charAt(j)); ++i; } else { result.add(new Token(Token.Type.INTEGER, sb.toString())); break; } } break; } } return result; } static Element parse(List<Token> tokens) { BinaryOperation result = new BinaryOperation(); boolean haveLHS = false; for (int i = 0; i < tokens.size(); ++i) { Token token = tokens.get(i); //System.out.println(token.toString()); switch (token.type){ case INTEGER: Integer integer = new Integer(java.lang.Integer.parseInt(token.text)); if(!haveLHS){ result.left = integer; haveLHS = true; } else { result.right = integer; } break; case PLUS: result.type = BinaryOperation.Type.ADDITION; break; case MINUS: result.type = BinaryOperation.Type.SUBTRACTION; break; case LPAREN: int j = i; for (; j < tokens.size(); ++j) if(tokens.get(j).type == Token.Type.RPAREN) break; List<Token> subexpression = tokens.stream() .skip(i+1) .peek(e -> System.out.println(e)) .limit((j-i)-1) .collect(Collectors.toList()); Element element = parse(subexpression); if(!haveLHS){ result.left = element; haveLHS = true; } else { result.right = element; } i = j; break; } } return result; } public static void main(String[] args) { String input = "(13+4)-(12+1)"; List<Token> tokens = lex(input); System.out.println(tokens.stream() .map(t -> t.toString()) .collect(Collectors.joining("\t"))); Element parsed = parse(tokens); System.out.println( input + " = " + parsed.eval()); } } |
The Iterator Pattern traverses/walk data structures, processing the elements one at a time, for example you can taverse a binary tree. The Iterator will keep a reference of the current element within the data structure. Many of the Collection Framework structures have there own built-in iterators.
Iterator example (tree traverse) | package uk.co.datadisk.Behavioral.iterator; // Traversing data structures import java.util.Iterator; import java.util.Spliterator; import java.util.function.Consumer; class Node<T> { public T value; public Node<T> left, right, parent; public Node(T value) { this.value = value; } public Node(T value, Node<T> left, Node<T> right) { this.value = value; this.left = left; this.right = right; left.parent = right.parent = this; } } class InOrderIterator<T> implements Iterator<T> { private Node<T> current, root; private boolean yieldedStart; public InOrderIterator(Node<T> root) { this.root = current = root; while(current.left != null) current = current.left; } private boolean hasRightmostParent(Node<T> node){ if(node.parent == null) return false; else { return (node == node.parent.left) || hasRightmostParent(node.parent); } } @Override public boolean hasNext() { return current.left != null || current.right != null || hasRightmostParent(current); } @Override public T next() { if (!yieldedStart) { yieldedStart = true; return current.value; } if (current.right != null) { current = current.right; while (current.left != null) current = current.left; return current.value; } else { Node<T> p = current.parent; while (p != null && current == p.right) { current = p; p = p.parent; } current = p; return current.value; } } @Override public void remove() { } @Override public void forEachRemaining(Consumer<? super T> action) { } } class BinTree<T> implements Iterable<T> { private Node<T> root; public BinTree(Node<T> root) { this.root = root; } @Override public Iterator<T> iterator() { return new InOrderIterator<>(root); } @Override public void forEach(Consumer<? super T> action) { for(T item : this){ action.accept(item); } } @Override public Spliterator<T> spliterator() { return null; } } public class Main_Iterator_1 { public static void main(String[] args) { // 1 // / \ // 2 3 // 213 Node<Integer> root = new Node<>(1, new Node<>(2), new Node<>(3)); InOrderIterator<Integer> it = new InOrderIterator<>(root); while(it.hasNext()){ System.out.print("" + it.next() + ","); } System.out.println(); BinTree<Integer> tree = new BinTree<>(root); for(int n : tree){ System.out.print("" + n + ","); } System.out.println(); } } |
Iterator example (tree array-backed properties) | package uk.co.datadisk.Behavioral.iterator; // Array-backed properties import java.util.Iterator; import java.util.Spliterator; import java.util.function.Consumer; import java.util.stream.IntStream; // This class is a bit complex plus the properties could change (we hard coded 3.0) class SimpleCreature { private int strength, agility, intelligence; public int max(){ return Math.max(strength, Math.max(agility, intelligence)); // might have to change if we add extra property } public int sum(){ return strength + agility + intelligence; // might have to change if we add extra property } public double average(){ return sum() / 3.0; // bad coding } public int getStrength() { return strength; } public void setStrength(int strength) { this.strength = strength; } public int getAgility() { return agility; } public void setAgility(int agility) { this.agility = agility; } public int getIntelligence() { return intelligence; } public void setIntelligence(int intelligence) { this.intelligence = intelligence; } } // Using Array-Backed properties class Creature implements Iterable<Integer> { private int[] stats = new int[3]; // Makes it easy to add extra properties // Use constants private final int STRENGTH = 0; private final int AGILITY = 1; private final int INTELLIGENCE = 2; public int getStrength(){ return stats[STRENGTH];} public void setStrength(int value) { stats[STRENGTH] = value;} public int getAgility(){ return stats[AGILITY];} public void setAgility(int value) { stats[AGILITY] = value;} public int getIntelligence(){ return stats[INTELLIGENCE];} public void setIntelligence(int value) { stats[INTELLIGENCE] = value;} // Never need to change public int sum(){ return IntStream.of(stats).sum(); } public int max(){ return IntStream.of(stats).max().getAsInt(); } public double average() { return IntStream.of(stats).average().getAsDouble(); } @Override public Iterator<Integer> iterator() { return IntStream.of(stats).iterator(); } @Override public void forEach(Consumer<? super Integer> action) { for (int x : stats){ action.accept(x); } } @Override public Spliterator<Integer> spliterator() { return IntStream.of(stats).spliterator(); } } public class Main_Iterator_2 { public static void main(String[] args) { Creature creature = new Creature(); creature.setAgility(12); creature.setIntelligence(13); creature.setStrength(17); System.out.println("Creature has a max stat of " + creature.max()); System.out.println("Creature has a total stat of " + creature.sum()); System.out.println("Creature has a average stat of " + creature.average()); } } |
The Mediator Pattern facilitates communication between components, bidirectional communication with connected components. Mediators can have functions that the components can call, a good example of this is a chat room, the components (clients) won't be aware of each other but communicate via the central component, another good example is a online game.
Mediator example (chat room) | package uk.co.datadisk.Behavioral.mediator; // Facilitates communication between components, bidirectional communication with connected components // Mediator can have functions that the components can call // solution is have a central component that facilitates communication (for example a chat room), the components // won't be aware of each other but communicate via the central component import java.util.ArrayList; import java.util.List; // No Person can reference another Person, they have to go through the mediator class Person { public String name; public ChatRoom room; // THE MEDIATOR private List<String> chatlog = new ArrayList<>(); public Person(String name) { this.name = name; } public void receive(String sender, String message){ String s = sender + ": '" + message + "'"; System.out.println("[" + name + "'s chat session] " + s); chatlog.add(s); } public void say(String message){ room.broadcast(name, message); } public void privateMessage(String who, String message){ room.message(name, who, message); } } // Mediator (the central component) class ChatRoom { private List<Person> people = new ArrayList<>(); public void join(Person p){ String joinMsg = p.name + " has joined the room"; broadcast("room", joinMsg); p.room = this; people.add(p); } public void broadcast(String source, String message){ for(Person person : people){ if(!person.name.equals(source)) person.receive(source, message); } } public void message(String source, String destination, String message){ people.stream() .filter(p -> p.name.equals(destination)) .findFirst() .ifPresent(person -> person.receive(source, message)); } public void chatPeopleList() { System.out.print("CHAT ROOM: "); for(Person p : people){ System.out.print(p.name + ", "); } System.out.println(); } } public class Main_Mediator_1 { public static void main(String[] args) { ChatRoom room = new ChatRoom(); Person paul = new Person("Paul"); Person lorraine = new Person("Lorraine"); room.join(paul); room.join(lorraine); paul.say("Hi all"); lorraine.say("Hello"); Person dominic = new Person("Dominic"); room.join(dominic); dominic.say("Hi all"); lorraine.privateMessage("Dominic", "Dominic glad you can join us!"); room.chatPeopleList(); } } |
Mediator example (event broker) | package uk.co.datadisk.Behavioral.mediator; // Reactive Extensions Event Broker import io.reactivex.Observable; import io.reactivex.Observer; import java.util.ArrayList; import java.util.List; // Mediator (the central component) class EventBroker extends Observable<Integer> { public List<Observer<? super Integer>> observers = new ArrayList<>(); @Override protected void subscribeActual(Observer<? super Integer> observer) { observers.add(observer); } public void publish(int n){ for(Observer<? super Integer> o : observers){ o.onNext(n); } } } // Talks to the broker class FootballPlayer { private EventBroker broker; // central component private int goalsScored = 0; public String name; public FootballPlayer(EventBroker broker, String name) { this.broker = broker; this.name = name; } public void score(){ broker.publish(++goalsScored); } } // Talks to the broker class FootballCoach { public FootballCoach(EventBroker broker){ // the broker is the central component broker.subscribe( i -> { System.out.println("Hey, you scored " + i + " goals"); }); } } public class Main_Mediator_2 { public static void main(String[] args) { EventBroker broker = new EventBroker(); FootballPlayer player = new FootballPlayer(broker, "Valle"); FootballCoach coach = new FootballCoach(broker); player.score(); player.score(); player.score(); } } |
The Memento Pattern keeps an Objects state (a point in time) so that you can return to that state if you wish too, a token/handle representing the system state will let us rollback to the state when the token was generated (the token is immutable). Preserving state so that it can be undone later, undo a step in a game, undo a trading transaction, word processing undo button, etc. The difference between Memento and Command Patterns is that the memento pattern deals with state and command pattern deals with commands/actions.
Memento example | package uk.co.datadisk.Behavioral.memento; // Keep a memento of an Object's state to return to that state // You can also record state using the Command Pattern // You can capture snapshots at particular points in time // A token/handle representing the system state and lets us rollback // to the state when the token was generated (the token is immutable) class Memento { // No setters so you cannot change once set private int balance; public Memento(int balance) { this.balance = balance; } public int getBalance() { return balance; } } class BankAccount { private int balance; public BankAccount(int balance) { this.balance = balance; } public void withdraw(int amount){ balance -= amount; } public Memento deposit(int amount){ balance += amount; return new Memento(balance); } public void restore(Memento m){ balance = m.getBalance(); } @Override public String toString() { return "BankAccount{" + "balance=" + balance + '}'; } } public class Main_Memento_1 { public static void main(String[] args) { BankAccount ba = new BankAccount(100); Memento m1 = ba.deposit(50); // balance = 150 Memento m2 = ba.deposit(25); // balance = 175 System.out.println(ba); // restore to m1 ba.restore(m1); System.out.println(ba); // restore to m2 ba.restore(m2); System.out.println(ba); } } |
The Null Object Pattern does not have any behaviors, however a no-op object that conforms to the required interface, satisfying a dependency requirement of some other object but it does not do anything, its a good idea to make the nullObject a singleton. It will be used where you need to pass something but you don't really want it to do anything, it just satisfies a requirement.
Null Object example | package uk.co.datadisk.Behavioral.nullObject; // A behavioral design pattern with on behaviors // A no-op object that conforms to the required interface, satisfying a dependency // requirement of some other object but it does not do anything // Its a good idea to make the nullObject a singleton import java.lang.reflect.Proxy; interface Log{ void info(String msg); void warn(String msg); } class ConsoleLog implements Log { @Override public void info(String msg) { System.out.println(msg); } @Override public void warn(String msg) { System.out.println("WARNING: " + msg); } } class BankAccount { private Log log; private int balance; public BankAccount(Log log) { this.log = log; } public void deposit(int amount){ balance += amount; log.info("deposited " + amount); } } // Performance is better using this Way final class NullLog implements Log { //Leave all the implemented methods blank @Override public void info(String msg) {} @Override public void warn(String msg) {} } // Use for quick and dirty public class Main_Null_Object_1 { @SuppressWarnings("unchecked") public static <T> T noOp(Class<T> itf) { return (T) Proxy.newProxyInstance( itf.getClassLoader(), new Class<?>[]{itf}, (proxy, method, args) -> { if (method.getReturnType().equals(Void.TYPE)) return null; else return method.getReturnType().getConstructor().newInstance(); }); } public static void main(String[] args) { ConsoleLog log1 = new ConsoleLog(); BankAccount ba1 = new BankAccount(log1); ba1.deposit(100); // we don't want any logging // using null will cause a NullPointerException //BankAccount ba2 = new BankAccount(null); //ba2.deposit(100); // The below two don't log NullLog log2 = new NullLog(); BankAccount ba3 = new BankAccount(log2); ba3.deposit(75); // Dynamic Null Object Log log3 = noOp(Log.class); BankAccount ba4 = new BankAccount(log3); ba4.deposit(50); } } |
The Observer Pattern gets informed when certain things/events happen, it listens to events and notifies when they occur. In observer pattern, the object that watch on the state of another object are called Observer and the object that is being watched is called Subject. Examples of this pattern is magazine publishing, Facebook notifications, Software updates (any thing with publish and subscribe).
Observer example (old way - use Observer and Observerable) | package uk.co.datadisk.Behavioral.observer; // Get informed when certain things/events happen // Listen to events and notify when they occur // Old way was to use addXxxListener() import java.util.ArrayList; import java.util.List; class PropertyChangedEventArgs<T> { public T source; public String propertyName; public Object newValue; public PropertyChangedEventArgs(T source, String propertyName, Object newValue) { this.source = source; this.propertyName = propertyName; this.newValue = newValue; } } // this part is looking at something interface Observer<T> { void handle(PropertyChangedEventArgs<T> args); } class Observable<T> { private List<Observer<T>> observers = new ArrayList<>(); public void subscribe(Observer<T> observer){ observers.add(observer); } public void propertyChanged(T source, String propertyName, Object newValue){ for(Observer<T> o : observers){ o.handle(new PropertyChangedEventArgs<T>( source, propertyName, newValue )); } } } // You can only extend once and thus you cannot use a base class class Person extends Observable<Person> { private int age; public int getAge() { return age; } public void setAge(int age) { if (this.age == age) return; this.age = age; // this is the trigger propertyChanged(this, "age", age); } } public class Main_Observer_1 implements Observer<Person> { public static void main(String[] args) { new Main_Observer_1(); } public Main_Observer_1() { Person person = new Person(); person.subscribe(this); for (int i = 20; i < 24; ++i) { person.setAge(i); } } @Override public void handle(PropertyChangedEventArgs<Person> args) { System.out.println("Person's " + args.propertyName + " has changed to " + args.newValue); } } |
Observer example (newer way - use Event class) | package uk.co.datadisk.Behavioral.observer; // Event class (don't use Observers and Observables) import java.util.HashMap; import java.util.Map; import java.util.function.Consumer; class Event2<TArgs> { private int count = 0; // Using a Consumer means you can use Lambda Functions private Map<Integer, Consumer<TArgs>> handlers = new HashMap<>(); public Subscription addHandler(Consumer<TArgs> handler){ int i = count; handlers.put(count++, handler); return new Subscription(this, i); } public void fire(TArgs args){ for(Consumer<TArgs> handler : handlers.values()){ handler.accept(args); } } public void displayHandlers() { for (Map.Entry<Integer, Consumer<TArgs>> handler : handlers.entrySet()){ System.out.println("Key: " + handler.getKey() + " Value: " + handler.getValue()); } } public class Subscription implements AutoCloseable { private Event2<TArgs> event; // id is the key into the handlers Map private int id; public Subscription(Event2<TArgs> event, int id) { this.event = event; this.id = id; } // used to unsubscribe @Override public void close() /*throws Exception*/ { event.handlers.remove(id); } } } class PropertyChangedEventArgs2 { private Object source; public String propertyName; public PropertyChangedEventArgs2(Object source, String propertyName) { this.source = source; this.propertyName = propertyName; } } // Notice this class is not extend and thus you can use a base class class Person2{ public Event2<PropertyChangedEventArgs2> propertyChanged = new Event2<>(); private int age; public int getAge() { return age; } public void setAge(int age) { if(this.age == age) return; this.age = age; //System.out.println("This: " + this); propertyChanged.fire(new PropertyChangedEventArgs2(this, "age")); } } public class Main_Observer_2 { public static void main(String[] args) { Person2 person1 = new Person2(); Event2<PropertyChangedEventArgs2>.Subscription sub = person1.propertyChanged.addHandler(x -> { System.out.println("Person's " + x.propertyName + " has changed to " + person1.getAge()); }); Person2 person2 = new Person2(); Event2<PropertyChangedEventArgs2>.Subscription sub2 = person2.propertyChanged.addHandler(x -> { System.out.println("Person's " + x.propertyName + " has changed to " + person2.getAge()); }); System.out.println("--------- Handlers ----------------"); person1.propertyChanged.displayHandlers(); person2.propertyChanged.displayHandlers(); System.out.println("---------------- Demo Code ---------------"); person1.setAge(17); person1.setAge(18); // We unsubscribe so no messages will be outputted from this point forward sub.close(); person1.setAge(19); } } |
The State Pattern changes in state can be explicit or in response to event (Observer Pattern), depending on your state machine you go from one state to another (trigger something to tranform from one state to another state), a formalized construct which manages state and transitions is called state machine. Examples of this are Gumball Machine, Jukebox, Traffic lights, Kettle (on/off), Record Player, etc.
State example (old way) | package uk.co.datadisk.Behavioral.state; // Changes in state can be explicit or in response to event (Observer Pattern) // Depending on your state machine you go from one state to another // A formalized construct which manages state and transitions is called state machine // Old way to do State (GOF) class State { void on(LightSwitch ls){ System.out.println("Light is already on"); } void off(LightSwitch ls){ System.out.println("Light is already off"); } } class LightSwitch { private State state; public LightSwitch() { state = new OffState(); } void on() { state.on(this); } void off() { state.off(this); } public void setState(State state) { this.state = state; } } class OnState extends State { public OnState() { System.out.println("Light turned on"); } @Override void off(LightSwitch ls) { System.out.println("Switching light off"); ls.setState(new OffState()); } } class OffState extends State { public OffState() { System.out.println("Light turned off"); } @Override void on(LightSwitch ls) { System.out.println("Switching light on"); ls.setState(new OnState()); } } public class Main_State_1 { public static void main(String[] args) { LightSwitch lightSwitch = new LightSwitch(); lightSwitch.on(); lightSwitch.off(); lightSwitch.off(); } } |
State example (newer way) | package uk.co.datadisk.Behavioral.state; // Newer way to implement State2 Pattern import org.javatuples.Pair; import java.io.BufferedReader; import java.io.InputStreamReader; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; enum State2 { OFF_HOOK, // starting state2 ON_HOOK, // terminal (final) state2 CONNECTING, CONNECTED, ON_HOLD } enum Trigger { CALL_DIALED, HUNG_UP, CALL_CONNECTED, PLACED_ON_HOLD, TAKEN_OFF_HOLD, LEFT_MESSAGE, STOP_USING_PHONE } public class Main_State_2 { private static Map<State2, List<Pair<Trigger, State2>>> rules = new HashMap<>(); static { rules.put(State2.OFF_HOOK, Arrays.asList( // you can dial a call which means you move to connecting state2 // or you can stop using phone thus you move into on hook state2 new Pair<>(Trigger.CALL_DIALED, State2.CONNECTING), new Pair<>(Trigger.STOP_USING_PHONE, State2.ON_HOOK) )); rules.put(State2.CONNECTING, Arrays.asList( new Pair<>(Trigger.HUNG_UP, State2.OFF_HOOK), new Pair<>(Trigger.CALL_CONNECTED, State2.CONNECTED) )); rules.put(State2.CONNECTING, Arrays.asList( new Pair<>(Trigger.LEFT_MESSAGE, State2.OFF_HOOK), new Pair<>(Trigger.HUNG_UP, State2.OFF_HOOK), new Pair<>(Trigger.PLACED_ON_HOLD, State2.ON_HOOK) )); rules.put(State2.ON_HOLD, Arrays.asList( new Pair<>(Trigger.TAKEN_OFF_HOLD, State2.CONNECTED), new Pair<>(Trigger.HUNG_UP, State2.OFF_HOOK) )); } private static State2 currentState2 = State2.OFF_HOOK; private static State2 exitState2 = State2.ON_HOOK; public static void main(String[] args) { BufferedReader console = new BufferedReader(new InputStreamReader(System.in)); while(true){ System.out.println("The phone is currently "+ currentState2); System.out.println("Select a trigger"); for (int i = 0; i < rules.get(currentState2).size(); ++i) { Trigger trigger = rules.get(currentState2).get(i).getValue0(); System.out.println("" + i + ". " + trigger); } boolean parseOK = true; int choice = 0; do { try { System.out.println("Please enter your choice: "); choice = Integer.parseInt(console.readLine()); parseOK = choice >= 0 && choice < rules.get(currentState2).size(); } catch (Exception e) { parseOK = false; e.printStackTrace(); } } while(!parseOK); currentState2 = rules.get(currentState2).get(choice).getValue1(); if(currentState2 == exitState2){ break; } } System.out.println("And we are done"); } } |
The Strategy Pattern is used to change a class behavior or its algorithm can be changed at run time (dynamic or static), many algorithms can be decomposed into higher and lower level parts the high level parts can then be reused, examples are a gamer figure could walk, run or swim (we don't know until he/she does it), the output of some text (could be XML, JSON, HTML) again we don't know until the users tells what he/she wants during runtime.
Strategy example (dynamic) | package uk.co.datadisk.Behavioral.strategy; // System behavior partially specified at runtime // Many algorithms can be decomposed into higher and lower level parts // the high level parts can then be reused // High level - get cup, fill kettle, boil water, pour into cup (reuse) // Low level - put teabag in to water, add chocolate power, add milk, add lemon, add sugar (specific strategy) import java.util.Arrays; import java.util.List; // Dynamic Strategy enum OutputFormat { MARKDOWN, HTML } // Base Class interface ListStrategy { default void start(StringBuilder sb) {} void addListItem(StringBuilder sb, String item); default void end(StringBuilder sb) {} } class MarkdownListStrategy implements ListStrategy { @Override public void addListItem(StringBuilder sb, String item) { sb.append(" * ").append(item).append(System.lineSeparator()); } } class HtmlListStrategy implements ListStrategy { @Override public void start(StringBuilder sb) { sb.append("<ul>").append(System.lineSeparator()); } @Override public void addListItem(StringBuilder sb, String item) { sb.append(" <li>").append(item).append("</li>").append(System.lineSeparator()); } @Override public void end(StringBuilder sb) { sb.append("</ul>").append(System.lineSeparator()); } } class TextProcessor { private StringBuilder sb = new StringBuilder(); private ListStrategy listStrategy; public TextProcessor(OutputFormat format){ setOutputFormat(format); } public void setOutputFormat(OutputFormat format){ switch (format) { case MARKDOWN: listStrategy = new MarkdownListStrategy(); break; case HTML: listStrategy = new HtmlListStrategy(); break; } } public void appendList(List<String> items){ listStrategy.start(sb); for(String item: items){ listStrategy.addListItem(sb, item); } listStrategy.end(sb); } public void clear(){ sb.setLength(0); } @Override public String toString() { return sb.toString(); } } public class Main_Strategy_1 { public static void main(String[] args) { TextProcessor tp = new TextProcessor(OutputFormat.MARKDOWN); tp.appendList(Arrays.asList("liberte", "egalite", "fraternite")); System.out.println(tp); tp.clear(); tp.setOutputFormat(OutputFormat.HTML); tp.appendList(Arrays.asList("liberte", "egalite", "fraternite")); System.out.println(tp); } } |
Strategy example (static) | package uk.co.datadisk.Behavioral.strategy; import java.util.Arrays; import java.util.List; import java.util.function.Supplier; // Static Strategy enum OutputFormat2 { MARKDOWN, HTML } // Base Class interface ListStrategy2 { default void start(StringBuilder sb) {} void addListItem(StringBuilder sb, String item); default void end(StringBuilder sb) {} } class MarkdownListStrategy2 implements ListStrategy2 { @Override public void addListItem(StringBuilder sb, String item) { sb.append(" * ").append(item).append(System.lineSeparator()); } } class HtmlListStrategy2 implements ListStrategy2 { @Override public void start(StringBuilder sb) { sb.append("<ul>").append(System.lineSeparator()); } @Override public void addListItem(StringBuilder sb, String item) { sb.append(" <li>").append(item).append("</li>").append(System.lineSeparator()); } @Override public void end(StringBuilder sb) { sb.append("</ul>").append(System.lineSeparator()); } } class TextProcessor2<LS extends ListStrategy2> { private StringBuilder sb = new StringBuilder(); private LS listStrategy2; public TextProcessor2(Supplier<? extends LS> ctor) { // ctor = constructor // baking in the strategy listStrategy2 = ctor.get(); } public void appendList(List<String> items){ listStrategy2.start(sb); for(String item: items){ listStrategy2.addListItem(sb, item); } listStrategy2.end(sb); } public void clear(){ sb.setLength(0); } @Override public String toString() { return sb.toString(); } } public class Main_Strategy_2 { public static void main(String[] args) { TextProcessor2<MarkdownListStrategy2> tp1 = new TextProcessor2<>(MarkdownListStrategy2::new); tp1.appendList(Arrays.asList("liberte", "egalite", "fraternite")); System.out.println(tp1); // cannot switch strategy dynamically as it baked in (static) TextProcessor2<HtmlListStrategy2> tp2 = new TextProcessor2<>(HtmlListStrategy2::new); tp2.appendList(Arrays.asList("liberte", "egalite", "fraternite")); System.out.println(tp2); } } |
The Template Method Pattern provides high-level blueprints for an algorithm to be completed by inheritors, the template method does the same as the strategy pattern but uses inheritance, the overall algorithm makes use of abstract member, inheritors override the abstract members and parent template method invoked, examples are game structure template, computer base template (add cpu, add memory, etc).
The Template Method Pattern can be confused with the strategy class, Strategy pattern defines a family of algorithms and makes them interchangeable. Client code can use different algorithms since the algorithms are encapsulated. Template method defines the outline of an algorithm and lets subclasses part of the algorithm's implementation. So you can have different implementations of an algorithms steps but retain the algorithm's structure
Template Method example | package uk.co.datadisk.Behavioral.templateMethod; // A high-level blueprint for an algorithm to be completed by inheritors // Algorithms remember can be decomposed into common parts + specifics // The template method does the same as the strategy pattern but uses inheritance // - overall algorithm makes use of abstract member // - inheritors override the abstract members // - parent template method invoked // Allow us to define the skeleton of the algorithm with concrete implementation defined in subclasses // This can be extended by any game (Base Class) abstract class Game { protected int currentPlayer; protected final int numberOfPlayers; public Game(int numberOfPlayers) { this.numberOfPlayers = numberOfPlayers; } public void run(){ start(); while(!haveWinner()) { takeTurn(); } System.out.println("Player "+ getWinningPlayer() + " wins"); } protected abstract int getWinningPlayer(); protected abstract void takeTurn(); protected abstract boolean haveWinner(); protected abstract void start(); } class Chess extends Game { private int maxTurns = 10; private int turn = 1; public Chess() { super(2); } @Override protected int getWinningPlayer() { return 0; } @Override protected void takeTurn() { System.out.println("Turn " + (turn++) + " taken by player " + currentPlayer); currentPlayer = (currentPlayer+1) % numberOfPlayers; } @Override protected boolean haveWinner() { return turn == maxTurns; } @Override protected void start() { System.out.println("Starting a game of chess"); } } public class Main_Template_Method_1 { public static void main(String[] args) { new Chess().run(); } } |
The Visitor Pattern allows adding extra behaviors to entire hierarchies of classes, a pattern where a component (visitor) is allowed to traverse the entire inheritance hierarchy. Implemented by propagating a single visit() method throughout the entire hierarchy.
Visitor example (intrusive) | package uk.co.datadisk.Behavioral.visitor; // Allows adding extra behaviors to entire hierarchies of classes // A pattern where a component (visitor) is allowed to traverse // the entire inheritance hierarchy. Implemented by propagating // a single visit() method throughout the entire hierarchy // Intrusive Visitor (this violates the open/close principle) abstract class Expression { // By adding this (at a later) date we broke the open/close principle public abstract void print(StringBuilder sb); } class DoubleExpression extends Expression { private double value; public DoubleExpression(double value) { this.value = value; } // We had to add this method (not good) @Override public void print(StringBuilder sb) { sb.append(value); } } class AdditionExpression extends Expression { private Expression left, right; public AdditionExpression(Expression left, Expression right) { this.left = left; this.right = right; } // We had to add this method (not good) @Override public void print(StringBuilder sb) { sb.append("("); left.print(sb); sb.append("+"); right.print(sb); sb.append(")"); } } public class Main_Visitor_1 { public static void main(String[] args) { // 1+(2+3) AdditionExpression e = new AdditionExpression( new DoubleExpression(1), new AdditionExpression( new DoubleExpression(2), new DoubleExpression(3) ) ); StringBuilder sb = new StringBuilder(); e.print(sb); System.out.println(sb); } } |
Visitor example (reflective) | package uk.co.datadisk.Behavioral.visitor; // Reflective Visitor abstract class Expression2 { } class DoubleExpression2 extends Expression2 { public double value; public DoubleExpression2(double value) { this.value = value; } } class AdditionExpression2 extends Expression2 { public Expression2 left, right; public AdditionExpression2(Expression2 left, Expression2 right) { this.left = left; this.right = right; } } class ExpressionPrinter { public static void print(Expression2 e, StringBuilder sb){ // Using reflection to figure out the instances of a particular type // Performance will be slow when using Reflection if( e.getClass() == DoubleExpression2.class){ sb.append(((DoubleExpression2)e).value); // you might have issues accessing if private (use getter) } else if (e.getClass() == AdditionExpression2.class){ AdditionExpression2 ae = (AdditionExpression2)e; sb.append("("); print(ae.left, sb); sb.append("+"); print(ae.right, sb); sb.append(")"); } } } public class Main_Visitor_2 { public static void main(String[] args) { // 1+(2+3) AdditionExpression2 e = new AdditionExpression2( new DoubleExpression2(1), new AdditionExpression2( new DoubleExpression2(2), new DoubleExpression2(3) ) ); StringBuilder sb = new StringBuilder(); ExpressionPrinter.print(e, sb); System.out.println(sb); } } |
Vistor example (double dispatch) | package uk.co.datadisk.Behavioral.visitor; // Class Visitor (Double Dispatch) interface ExpressionVisitor { void visit(DoubleExpression3 e); void visit(AdditionExpression3 e); } abstract class Expression3 { public abstract void accept(ExpressionVisitor visitor); } class DoubleExpression3 extends Expression3 { public double value; public DoubleExpression3(double value) { this.value = value; } @Override public void accept(ExpressionVisitor visitor) { visitor.visit(this); } } class AdditionExpression3 extends Expression3 { public Expression3 left, right; public AdditionExpression3(Expression3 left, Expression3 right) { this.left = left; this.right = right; } @Override public void accept(ExpressionVisitor visitor) { visitor.visit(this); } } class ExpressionPrinter3 implements ExpressionVisitor { private StringBuilder sb = new StringBuilder(); @Override public void visit(DoubleExpression3 e) { sb.append(e.value); } @Override public void visit(AdditionExpression3 e) { sb.append("("); e.left.accept(this); sb.append("+"); e.right.accept(this); sb.append(")"); } @Override public String toString() { return sb.toString(); } } class ExpressionCalculator implements ExpressionVisitor { public double result; @Override public void visit(DoubleExpression3 e) { result = e.value; } @Override public void visit(AdditionExpression3 e) { e.left.accept(this); double a = result; e.right.accept(this); double b = result; System.out.println("A: " + a + " B: " + b); result = a+b; } } public class Main_Visitor_3 { public static void main(String[] args) { // 1+(2+3) AdditionExpression3 e = new AdditionExpression3( new DoubleExpression3(1), new AdditionExpression3( new DoubleExpression3(2), new DoubleExpression3(3) ) ); ExpressionPrinter3 ep = new ExpressionPrinter3(); ep.visit(e); System.out.println(ep); ExpressionCalculator ec = new ExpressionCalculator(); ec.visit(e); System.out.println(ep + " = " + ec.result); } } |
Visitor example (acyclic) | package uk.co.datadisk.Behavioral.visitor; // Acyclic Visitor // get a performance degradation as you have to do type checking interface Visitor{} // marker interface interface ExpressionVisitor4 extends Visitor { void visit(Expression4 obj); } // The visit methods are now not related (independent) interface DoubleExpressionVisitor4 extends Visitor { void visit(DoubleExpression4 obj); } interface AdditionExpressionVisitor4 extends Visitor { void visit(AdditionExpression4 obj); } abstract class Expression4 { public void accept(Visitor visitor){ // Performance -, type checking if(visitor instanceof ExpressionVisitor4) ((ExpressionVisitor4)visitor).visit(this); } } class DoubleExpression4 extends Expression4 { public double value; public DoubleExpression4(double value) { this.value = value; } @Override public void accept(Visitor visitor){ // Performance -, type checking if(visitor instanceof DoubleExpressionVisitor4) ((DoubleExpressionVisitor4)visitor).visit(this); } } class AdditionExpression4 extends Expression4 { public Expression4 left, right; public AdditionExpression4(Expression4 left, Expression4 right) { this.left = left; this.right = right; } @Override public void accept(Visitor visitor){ // Performance -, type checking if(visitor instanceof AdditionExpressionVisitor4) ((AdditionExpressionVisitor4)visitor).visit(this); } } // Use have the ability to disable parts, for example you can remove // one of the interfaces with breaking anything class ExpressionPrinter4 implements DoubleExpressionVisitor4, AdditionExpressionVisitor4 { private StringBuilder sb = new StringBuilder(); @Override public void visit(DoubleExpression4 e) { sb.append(e.value); } @Override public void visit(AdditionExpression4 e) { sb.append("("); e.left.accept(this); sb.append("+"); e.right.accept(this); sb.append(")"); } @Override public String toString() { return sb.toString(); } } public class Main_Visitor_4 { public static void main(String[] args) { // 1+(2+4) AdditionExpression4 e = new AdditionExpression4( new DoubleExpression4(1), new AdditionExpression4( new DoubleExpression4(2), new DoubleExpression4(3) ) ); ExpressionPrinter4 ep = new ExpressionPrinter4(); ep.visit(e); System.out.println(ep); } } |
Visitor example (good basic) | package uk.co.datadisk.Behavioral.visitor; // Good basic example of a Visitor pattern interface Element { void accept(Visitor5 v); } class FOO implements Element { @Override public void accept(Visitor5 v) { v.visit(this); } public String getFOO() { return "FOO"; } } class BAR implements Element { @Override public void accept( Visitor5 v ) { v.visit( this ); } public String getBAR() { return "BAR"; } } class BAZ implements Element { @Override public void accept(Visitor5 v) { v.visit(this); } public String getBAZ() { return "BAZ"; } } interface Visitor5 { void visit(FOO foo); void visit(BAR bar); void visit(BAZ baz); } class UpVisitor implements Visitor5 { // Replaces a Switch statement, as you are using overloaded methods public void visit(FOO foo) { System.out.println("do Up on " + foo.getFOO()); } public void visit(BAR bar) { System.out.println("do Up on " + bar.getBAR()); } public void visit(BAZ baz) { System.out.println( "do Up on " + baz.getBAZ() ); } } class DownVisitor implements Visitor5 { // Replaces a Switch statement, as you are using overloaded methods public void visit(FOO foo) { System.out.println("do Down on " + foo.getFOO()); } public void visit(BAR bar) { System.out.println("do Down on " + bar.getBAR()); } public void visit(BAZ baz ) { System.out.println("do Down on " + baz.getBAZ()); } } public class Main_Visitor_5 { public static void main( String[] args ) { Element[] list = {new FOO(), new BAR(), new BAZ()}; UpVisitor up = new UpVisitor(); DownVisitor down = new DownVisitor(); // Pass the visitor to the accept method for (Element element : list) { element.accept(up); } for (Element element : list) { element.accept(down); } } } |