1. Template method mode*
Template method mode: define the skeleton of an algorithm in operation, and delay some steps to subclasses, so that subclasses can redefine some specific steps of an algorithm without changing the structure of an algorithm
abstract class MySort { // Template method: // The sorting algorithm is provided, but the order is not implemented public final void sort() { if (hook()) { orderBy(); } else { System.out.println("Default collation"); } } public abstract void orderBy(); // Hook method: the default is to do nothing. Subclasses can choose whether to override it as appropriate public boolean hook() { return true; } } class AscSort extends MySort { @Override public void orderBy() { System.out.println("Asc"); } } class DescSort extends MySort { @Override public void orderBy() { System.out.println("Desc"); } } class DefaultSort extends MySort { @Override public void orderBy() { } @Override public boolean hook() { return false; } } public class TemplateMethodClient { public static void main(String[] args) { MySort ascSort = new AscSort(); MySort descSort = new DescSort(); MySort defaultSort = new DefaultSort(); ascSort.sort(); descSort.sort(); defaultSort.sort(); } }
2. Command mode
Command mode: encapsulate a request into an object to separate the responsibility of issuing the command from the responsibility of executing the command, so that you can parameterize the customer with different requests, queue the request or record the request log, and support revocable operations
Command mode structure:
- Receiver: command receiver, the object that actually executes the command
- Command: the interface for executing the command, which declares the method for executing the command
- ConcreteCommand: the command object that implements the command interface, which is the implementation of "virtual"; It usually holds the command receiver and calls the function of the command receiver to complete the operation to be performed by the command
- Invoker: call the command object to execute the command, and usually hold the command object; This is the entry to use the command object
- Client: create a specific command object and set the receiver of the command object
// Command receiver: the object that actually executes the command class Tv { public void turnOn() { System.out.println("TV on"); } public void turnOff() { System.out.println("TV off"); } } // Interface for executing commands interface Command { void execute(); void undo(); } // Boot command class OnCommand implements Command { Tv tv; public OnCommand(Tv tv) { this.tv = tv; } @Override public void execute() { tv.turnOn(); } @Override public void undo() { tv.turnOff(); } } // Shutdown command class OffCommand implements Command { Tv tv; public OffCommand(Tv tv) { this.tv = tv; } @Override public void execute() { tv.turnOff(); } @Override public void undo() { tv.turnOn(); } } // Empty command: can be used to initialize each command in Invoker class EmptyCommand implements Command { @Override public void execute() { } @Override public void undo() { } } // Remote control class Invoker { Command onCommand = new EmptyCommand(); Command offCommand = new EmptyCommand(); public Invoker(Command onCommand, Command offCommand) { this.onCommand = onCommand; this.offCommand = offCommand; } public void turnOn() { onCommand.execute(); } public void turnOff() { offCommand.execute(); } } public class CommandClient { public static void main(String[] args) { Tv tv = new Tv(); Invoker invoker = new Invoker(new OnCommand(tv), new OffCommand(tv)); invoker.turnOn(); invoker.turnOff(); } }
3. Visitor mode
Visitor pattern: decouples the data structure from the operations acting on the structure, so that new operations acting on these elements can be defined without changing the class of each element
Visitor mode is suitable for systems with relatively stable data structure and easy to change algorithm, because visitor mode makes it easy to increase algorithm operation
The structure object is a prerequisite for using the visitor pattern, and the structure object must have a method to traverse its own objects. This is similar to the concept of collection in the Java language
Roles involved in visitor mode:
- Visitor: Abstract visitor role and declare an access operation interface for each concrete element role in the object structure
- ConcreteVisitor: a specific Visitor role that implements the interface declared by the Visitor
- Element: defines an operation that accepts access (accept()), which takes a visitor as a parameter
- ConcreteElement: a concrete element that implements the acceptance operation interface defined by the abstract element
- ObjectStructure: structure object role, which is a necessary role to use visitor pattern. It has the following characteristics: it can enumerate its elements; A high-level interface can be provided to allow visitors to access its elements
According to the Dahua design pattern, the visitor pattern is the most complex and difficult to understand!
import java.util.ArrayList; import java.util.List; // Abstract visitor role interface Visitor { void visitElementA(ConcreteElementA elementA); void visitElementB(ConcreteElementB elementB); } /** * Specific visitor roles: 1 * Different specific visitors may access elements in different ways */ class ConcreteVisitor1 implements Visitor { @Override public void visitElementA(ConcreteElementA elementA) { System.out.println("Access the element in mode 1 A"); } @Override public void visitElementB(ConcreteElementB elementB) { System.out.println("Access the element in mode 1 B"); } } // Specific visitor roles: 2 class ConcreteVisitor2 implements Visitor { @Override public void visitElementA(ConcreteElementA elementA) { System.out.println("Access the element in mode 2 A"); } @Override public void visitElementB(ConcreteElementB elementB) { System.out.println("Access the element in mode 2 B"); } } // Define an operation that accepts access interface Element { void accept(Visitor visitor); } /** * Specific elements: A * Dual dispatch used: * First dispatch: when the client calls, the specific state is passed to ConcreteElementA as a parameter * The second dispatch: pass the ConcreteElementA object itself as a parameter to visiterelementa */ class ConcreteElementA implements Element { @Override public void accept(Visitor visitor) { visitor.visitElementA(this); } } // Specific elements: B class ConcreteElementB implements Element { @Override public void accept(Visitor visitor) { visitor.visitElementB(this); } } // Structure object role class ObjectStructure { List<Element> elementList = new ArrayList<>(); public void attach(Element element) { elementList.add(element); } public void detach(Element element) { elementList.remove(element); } public void display(Visitor visitor) { for (Element element : elementList) { element.accept(visitor); } } } public class VisitorClient { public static void main(String[] args) { ObjectStructure objectStructure = new ObjectStructure(); objectStructure.attach(new ConcreteElementA()); objectStructure.attach(new ConcreteElementB()); objectStructure.attach(new ConcreteElementA()); objectStructure.display(new ConcreteVisitor1()); System.out.println("----------------------"); objectStructure.display(new ConcreteVisitor2()); } }
4. Iterator mode
Iterator pattern: provides a way to access the elements of an aggregate object sequentially without exposing the internal representation of the object
Roles involved in iterator pattern:
- Iterator: defines the interface for accessing and traversing elements
- Concrete iterator: implement iterator interface; Track the current position when traversing the aggregate
- Aggregate: defines the interface that creates the corresponding iterator object
- Concreteaggregate: implements the interface to create the corresponding iterator, which returns an appropriate instance of ConcreteIterator
import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; // The iterator interface uses off the shelf java util. Iterator interface // Concrete iterator class ListIterator<E> implements Iterator<E> { List<E> list; // The index of the next element to return int cursor = 0; // The index of the last element returned int lastRet = -1; public ListIterator(List<E> list) { this.list = list; } @Override public boolean hasNext() { return cursor < list.size(); } @Override public E next() { if (cursor >= list.size()) throw new NoSuchElementException(); lastRet = cursor; cursor++; return list.get(lastRet); } @Override public void remove() { if (lastRet < 0) throw new IllegalStateException(); list.remove(lastRet); cursor = lastRet; lastRet = -1; } } // polymerization interface MyCollection { Iterator<String> getIterator(); void addString(String s); } // Specific aggregation class MyList implements MyCollection { List<String> list = new ArrayList<>(); @Override public ListIterator<String> getIterator() { return new ListIterator<>(list); } @Override public void addString(String s) { list.add(s); } } public class IteratorClient { public static void main(String[] args) { MyList myList = new MyList(); myList.addString("1"); myList.addString("2"); myList.addString("3"); ListIterator<String> listIterator = myList.getIterator(); // 1 3 while (listIterator.hasNext()) { String next = listIterator.next(); if (next.equals("2")) listIterator.remove(); else System.out.print(next + " "); } } }
5. Observer mode*
Observer mode: defines one to many dependencies between objects. When the state of an object changes, all objects that depend on it are notified and automatically updated. In the observer mode, the subject is the publisher of the notification. It does not need to know who is its observer when sending the notification. Any number of observers can subscribe to and receive the notification
Roles involved in observer mode:
- Abstract Observer: define an interface for all concrete observers to update themselves when notified by the subject
- Concrete Observer: implement the update interface required by the abstract observer role to coordinate its own state with the subject state
- Abstract subject: it saves the references of all observer objects into a cluster, and each subject can have any number of observers. Abstract topics provide an interface to add and delete observer objects
- Concrete subject: store the relevant status into the specific observer object; Notify all registered observers when the internal state of a specific subject changes
java. util. The observable class uses the observer pattern
The publish subscribe mechanism is similar to the observer mode
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.ArrayList; import java.util.List; // Data to be updated @Data @NoArgsConstructor @AllArgsConstructor class UpdatedData { String username; String password; } // Abstract observer interface Observer { void update(UpdatedData updatedData); } // Specific observers: there can be more than one class ConcreteObserver implements Observer { UpdatedData updatedData = new UpdatedData(); @Override public void update(UpdatedData updatedData) { this.updatedData = updatedData; System.out.println(this.toString() + "Updated:" + this.updatedData); } } // Abstract theme interface Subject { void subscribe(Observer observer); void unsubscribe(Observer observer); void publish(UpdatedData updatedData); } // Specific theme: generally 1 class ConcreteSubject implements Subject { List<Observer> observerList = new ArrayList<>(); @Override public void subscribe(Observer observer) { System.out.println(observer + "subscribe"); observerList.add(observer); } @Override public void unsubscribe(Observer observer) { System.out.println(observer + "Unsubscribe"); if (observerList.contains(observer)) observerList.remove(observer); } @Override public void publish(UpdatedData updatedData) { System.out.println("release"); for (Observer observer : observerList) { observer.update(updatedData); } } } public class ObserverClient { public static void main(String[] args) { Subject subject = new ConcreteSubject(); Observer observer = new ConcreteObserver(); subject.subscribe(observer); subject.publish(new UpdatedData("root", "123456")); subject.unsubscribe(observer); } }
6. Intermediary model
Mediator pattern (mediator pattern): a mediation object is used to encapsulate a series of object interactions. Mediators make objects do not need to explicitly refer to each other, so that they are loosely coupled, and their interaction can be changed independently
Roles involved in the mediator model:
- Colleague: every colleague knows its mediator object and communicates with its mediator when he needs to communicate with other colleagues
- Concrete colleague: implements the colleague interface
- Mediator: the mediator defines an interface for communicating with colleagues
- Concrete mediator: a concrete mediator realizes cooperative behavior by coordinating various colleagues, and understands and maintains their colleagues
import java.util.ArrayList; import java.util.List; // colleague abstract class Colleague { Mediator mediator; public void setMediator(Mediator mediator) { this.mediator = mediator; } abstract void receive(); abstract void send(); } // Specific colleagues: 1 class ConcreteColleague1 extends Colleague { @Override void receive() { System.out.println("Specific colleague 1 received the request"); } @Override void send() { System.out.println("Specific colleague 1 sends a request"); // Ask the intermediary to forward mediator.relay(this); } } // Specific colleagues: 2 class ConcreteColleague2 extends Colleague { @Override void receive() { System.out.println("Specific colleague 2 received the request"); } @Override void send() { System.out.println("Specific colleague 2 sends a request"); // Ask the intermediary to forward mediator.relay(this); } } // tertium quid interface Mediator { void register(Colleague colleague); void relay(Colleague colleague); } // Specific intermediary class ConcreteMediator implements Mediator { List<Colleague> colleagueList = new ArrayList<>(); @Override public void register(Colleague colleague) { if (!colleagueList.contains(colleague)) { colleagueList.add(colleague); colleague.setMediator(this); } } @Override public void relay(Colleague colleague) { // Forward to all collages except Collage for (Colleague coll : colleagueList) { if (!coll.equals(colleague)) { coll.receive(); } } } } public class MediatorClient { public static void main(String[] args) { Mediator mediator = new ConcreteMediator(); Colleague colleague1 = new ConcreteColleague1(); Colleague colleague2 = new ConcreteColleague2(); mediator.register(colleague1); mediator.register(colleague2); colleague1.send(); System.out.println("---------------------"); colleague2.send(); } }
7. Memorandum mode
Memo mode is also called snapshot mode or Token mode: it captures the internal state of an object and saves the state outside the object without destroying the encapsulation. This will restore the object to its original saved state later
Roles involved in memo mode:
- Originator: it is responsible for creating a memo Memento to record its internal state at the current time, and can use the memo to restore its internal state. The originator can decide which internal states Memento stores as needed.
- Memo: it is responsible for storing the internal state of the Originator object and preventing other objects other than the Originator from accessing the memo. The memo has two interfaces: Caretaker can only see the narrow interface of the memo, and he can only pass the memo to other objects. However, the Originator can see the wide interface of the memo, allowing it to access all the data needed to return to the previous state.
- Caretaker (Manager): responsible for memo Memento, and cannot access or operate the contents of Memento.
import lombok.Data; import java.util.ArrayList; import java.util.List; // Initiator @Data class Originator { String state; public void setStateFromMemento(Memento memento) { state = memento.getState(); } public Memento createMementoToSaveState() { return new Memento(state); } } // memorandum class Memento { String state; public Memento(String state) { this.state = state; } public String getState() { return state; } } // controller class Caretaker { List<Memento> mementoList = new ArrayList<>(); void add(Memento memento) { mementoList.add(memento); } Memento get(int index) { return mementoList.get(index); } } public class MementoClient { public static void main(String[] args) { Caretaker caretaker = new Caretaker(); Originator originator = new Originator(); originator.state = "Status 0"; caretaker.add(originator.createMementoToSaveState()); originator.state = "Status 1"; caretaker.add(originator.createMementoToSaveState()); // Current status: Originator(state = state 1) System.out.println("Current status:" + originator); originator.setStateFromMemento(caretaker.get(0)); // After recovery, current status: Originator(state = 0) System.out.println("Current status after recovery:" + originator); } }
8. Interpreter mode
Interpreter pattern: given a language, define a representation of its grammar, and define an interpreter that uses the representation to interpret sentences in the language.
Roles involved in interpreter mode:
- Context: contains some global information outside the interpreter
- Abstract expression: declare an abstract interpretation operation. This interface is shared by all nodes in the abstract syntax tree.
- Terminator expression: implements the interpretation operation associated with the terminator in the grammar
- Nonterminal expression: implements interpretation operations for nonterminal characters in grammar; Interpretation generally calls the interpretation operation recursively
import java.util.HashMap; import java.util.LinkedList; import java.util.Map; /** * Abstract expression * Map<String, Integer> context Act as context */ interface AbstractExpression { // key in the Map is the variable name and value is the variable value int interpret(Map<String, Integer> context); } // Terminator expression class TerminalExpression implements AbstractExpression { String key; public TerminalExpression(String key) { this.key = key; } // According to the variable name, and then the corresponding value @Override public int interpret(Map<String, Integer> context) { return context.get(this.key); } } /** * Non terminator expression: * Each operator is only related to the numbers on its left and right sides, * But the numbers on the left and right sides may also be an analytical result, * And no matter which parsing result, it is of AbstractExpression type */ abstract class NonterminalExpression implements AbstractExpression { AbstractExpression left; AbstractExpression right; public NonterminalExpression(AbstractExpression left, AbstractExpression right) { this.left = left; this.right = right; } } // plus operator class AddOperator extends NonterminalExpression { public AddOperator(AbstractExpression left, AbstractExpression right) { super(left, right); } @Override public int interpret(Map<String, Integer> context) { return super.left.interpret(context) + super.right.interpret(context); } } // minus operator class SubOperator extends NonterminalExpression { public SubOperator(AbstractExpression left, AbstractExpression right) { super(left, right); } @Override public int interpret(Map<String, Integer> context) { return super.left.interpret(context) - super.right.interpret(context); } } public class InterpreterClient { public static void main(String[] args) { System.out.println("Define variables:"); Map<String, Integer> context = new HashMap<>(); context.put("a", 10); context.put("b", 5); context.put("c", 6); context.entrySet().forEach(entry -> System.out.println(entry.getKey() + " = " + entry.getValue())); System.out.println("Calculation expression:"); String expr = "a + b - c"; System.out.println(expr + " = " + calc(expr).interpret(context)); } // Calculation rules public static AbstractExpression calc(String expr) { LinkedList<AbstractExpression> stack = new LinkedList<>(); AbstractExpression left, right; for (int i = 0; i < expr.length(); i++) { switch (expr.charAt(i)) { case '+': left = stack.pop(); right = new TerminalExpression(expr.charAt(++i) + ""); stack.push(new AddOperator(left, right)); break; case '-': left = stack.pop(); right = new TerminalExpression(expr.charAt(++i) + ""); stack.push(new SubOperator(left, right)); break; case ' ': break; default: stack.push(new TerminalExpression(expr.charAt(i) + "")); break; } } return stack.pop(); } }
9. Status mode
State mode: allows an object to change its behavior when its internal state changes
State mode mainly solves the situation when the conditional expression controlling the state of an object is too complex. By transferring the state judgment logic to a series of classes representing different states, the complex judgment logic can be simplified
Roles involved in status mode:
- Context: define the interface of interest to customers and maintain an instance of a subclass of ConcreteState, which defines the current state
- State: define an interface to encapsulate the behavior related to a specific state of Context
- Concrete state subclass: each subclass implements a behavior related to a state of Context
Before using the state mode, you can draw a state transition diagram to analyze the relationship between various states
// environment class Context { State state; public Context(State state) { this.state = state; } void request() { state.handle(this); } } // state interface State { void handle(Context context); } // Specific status subclass: A class ConcreteStateA implements State { @Override public void handle(Context context) { context.state = new ConcreteStateB(); System.out.println("Removed from status A Switch to state B"); } } // Specific status subclass: B class ConcreteStateB implements State { @Override public void handle(Context context) { context.state = new ConcreteStateA(); System.out.println("Removed from status B Switch to state A"); } } public class StateClient { public static void main(String[] args) { // Initialization status is A Context context = new Context(new ConcreteStateA()); context.request(); context.request(); context.request(); } }
10. Strategic mode
Policy pattern: define a series of algorithms, encapsulate them one by one, and make them replaceable; The policy pattern allows the algorithm to vary independently of the customers using it
Roles involved in policy mode:
- Strategy: define the public interface of all supported algorithms; Context uses this interface to call a ConcreteStrategy
- Concrete Strategy: an algorithm that implements the Strategy interface
- Context: maintain a reference to the Strategy object
java.util.Arrays uses the policy mode: when sorting custom objects, they are sorted according to different sorting strategies
// strategy interface Strategy { void AlgorithmInterface(); } // Specific strategy A class ConcreteStrategyA implements Strategy { @Override public void AlgorithmInterface() { System.out.println("Using policy A"); } } // Specific strategy B class ConcreteStrategyB implements Strategy { @Override public void AlgorithmInterface() { System.out.println("Using policy B"); } } // context class Context { Strategy strategy; public Context(Strategy strategy) { this.strategy = strategy; } public void setStrategy(Strategy strategy) { this.strategy = strategy; } public void contextInterface() { this.strategy.AlgorithmInterface(); } } public class StrategyClient { public static void main(String[] args) { // Default usage policy A Context context = new Context(new ConcreteStrategyA()); context.contextInterface(); context.setStrategy(new ConcreteStrategyB()); context.contextInterface(); } }
11. Responsibility chain model*
Responsibility chain mode: each object is connected by its reference to its next family to form a chain. The request is passed on the chain until an object on the chain decides to process the request. The client issuing the request does not know which object in the chain will eventually process the request, which makes the system dynamically reorganize and allocate responsibilities without affecting the client
Roles involved in the responsibility chain model:
- Abstract handler: define an interface to process requests, and define a method to set and return references to the next house
- Concrete handler: after receiving a request, if the request can be processed, it will be processed; Otherwise, forward the request to its successor
// Abstract handler role abstract class Handler { protected int maxPriority; protected Handler successor; public Handler setSuccessor(Handler successor) { this.successor = successor; return this; } public final void request(int priority) { if (priority <= this.maxPriority) { handleRequest(); } else if (successor != null) { successor.request(priority); } else { System.out.println("Unable to process"); } } abstract void handleRequest(); } // Specific processor role: 1 class ConcreteHandler1 extends Handler { public ConcreteHandler1(int maxPriority) { this.maxPriority = maxPriority; } @Override void handleRequest() { System.out.println("First layer processing"); } } // Specific processor role: 2 class ConcreteHandler2 extends Handler { public ConcreteHandler2(int maxPriority) { this.maxPriority = maxPriority; } @Override void handleRequest() { System.out.println("Second layer treatment"); } } public class ChainOfResponsibilityClient { public static void main(String[] args) { // All requests are processed from the first Handler Handler handler = new ConcreteHandler1(10).setSuccessor( new ConcreteHandler2(100).setSuccessor( null)); handler.request(5); System.out.println("---------"); handler.request(50); System.out.println("---------"); handler.request(500); } }