Behavioral model

Posted by fishdish on Sat, 22 Jan 2022 04:04:13 +0100

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:

  1. Receiver: command receiver, the object that actually executes the command
  2. Command: the interface for executing the command, which declares the method for executing the command
  3. 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
  4. Invoker: call the command object to execute the command, and usually hold the command object; This is the entry to use the command object
  5. 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:

  1. Visitor: Abstract visitor role and declare an access operation interface for each concrete element role in the object structure
  2. ConcreteVisitor: a specific Visitor role that implements the interface declared by the Visitor
  3. Element: defines an operation that accepts access (accept()), which takes a visitor as a parameter
  4. ConcreteElement: a concrete element that implements the acceptance operation interface defined by the abstract element
  5. 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:

  1. Iterator: defines the interface for accessing and traversing elements
  2. Concrete iterator: implement iterator interface; Track the current position when traversing the aggregate
  3. Aggregate: defines the interface that creates the corresponding iterator object
  4. 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:

  1. Abstract Observer: define an interface for all concrete observers to update themselves when notified by the subject
  2. Concrete Observer: implement the update interface required by the abstract observer role to coordinate its own state with the subject state
  3. 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
  4. 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:

  1. Colleague: every colleague knows its mediator object and communicates with its mediator when he needs to communicate with other colleagues
  2. Concrete colleague: implements the colleague interface
  3. Mediator: the mediator defines an interface for communicating with colleagues
  4. 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:

  1. 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.
  2. 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.
  3. 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:

  1. Context: contains some global information outside the interpreter
  2. Abstract expression: declare an abstract interpretation operation. This interface is shared by all nodes in the abstract syntax tree.
  3. Terminator expression: implements the interpretation operation associated with the terminator in the grammar
  4. 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:

  1. Context: define the interface of interest to customers and maintain an instance of a subclass of ConcreteState, which defines the current state
  2. State: define an interface to encapsulate the behavior related to a specific state of Context
  3. 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:

  1. Strategy: define the public interface of all supported algorithms; Context uses this interface to call a ConcreteStrategy
  2. Concrete Strategy: an algorithm that implements the Strategy interface
  3. 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:

  1. Abstract handler: define an interface to process requests, and define a method to set and return references to the next house
  2. 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);
    }
}

Topics: Design Pattern