[detailed explanation of Java design pattern] [dark horse programmer] notes

Posted by tycragg on Tue, 25 Jan 2022 21:06:07 +0100

Design pattern related content

Design pattern classification

Create pattern

It is used to describe "how to create objects". Its main feature is to separate the creation and use of objects.
GOF book provides five creation modes: singleton, prototype, factory method, abstract factory and builder

Structural model

Used to describe how classes or objects form a larger structure according to a certain layout.
GOF book provides 7 structural modes, such as agent, adapter, bridge, decoration, appearance, element sharing and combination

Behavioral model

It is used to describe how classes or objects cooperate with each other to complete tasks that cannot be completed by a single object, and how to allocate responsibilities.
GOF book provides 11 behavioral models, including template method, strategy, command, responsibility chain, state, observer, mediator, iterator, visitor, memo and interpreter

UML diagram

Unified modeling language is a visual modeling language used to design software. Its characteristic is simple, unified, graphical, and can express the dynamic and static information in software design

Symbol for visibility
+: indicates public
-: indicates private
#: indicates protected

Full representation of attribute: visibility Name: type [= Default]
Full representation of method: visibility name (parameter list) [: return value]

Relationship between classes

Association relationship

Association relationship is a reference relationship between objects. It is used to represent the relationship between one kind of objects and another kind of objects. It is the most commonly used relationship between classes. It is divided into general association relationship, aggregation relationship and combination relationship

Association relationship can be divided into one-way Association, two-way Association and self association

Aggregation relation

Aggregation relationship is a kind of association relationship. It is a strong association relationship and a relationship between whole and part
The aggregation relationship is also realized through the member object, in which the member object is a part of the whole, but the member object can exist independently from the whole object, such as schools and teachers. If the school is gone, teachers can go to other schools

synthetic relation

Composition represents the whole part relationship between classes, but it is a stronger aggregation relationship
In the combination relationship, the whole object can control the life cycle of some objects. Once the whole object does not exist, some objects will not exist, and some objects cannot exist without the whole object. For example, in the relationship between head and mouth, if the head is gone, the mouth will not exist

Dependency

Dependency is a kind of usage relationship. It is an association mode with the weakest coupling between objects. It is a temporary association. In the code, the method of a class accesses some methods in another class (dependent class) through local, method parameters or calls to static methods to complete some responsibilities

Inheritance relationship

Inheritance relationship is the most coupling relationship between objects, which represents the relationship between general and special. It is the relationship between parent and child classes. It is an inheritance relationship

Implementation relationship

Implementation relationship is the relationship between an interface and an implementation class. In this relationship, the class implements the interface, and the operations in the class implement all the abstract methods declared in the interface

Software design principles

Opening and closing principle

It is open to extensions and closed to modifications.
When the program needs to be extended, you can't modify the original code to achieve a hot plug effect. In short, it is to make the program scalable and easy to maintain and upgrade

Richter substitution principle

Wherever a parent class can appear, a child class must appear.
When a subclass inherits from the parent class, try not to override the methods of the parent class except adding new methods to complete the new functions

Dependence Inversion Principle

High level modules should not rely on low-level modules, and both should rely on their abstraction; Abstract should not rely on details, details should rely on abstraction.
In short, it requires programming the abstraction rather than the implementation, which reduces the coupling between the customer and the implementation module

Interface isolation principle

The client should not be forced to rely on methods it does not use; The dependence of one class on another should be based on the smallest interface

Dimitt's law

Dimitri's law is also called the principle of least knowledge
If the two software entities do not need to communicate directly, there should be no direct related call, and the call can be forwarded with the third party of the Forbidden City. Its purpose is to reduce the coupling between classes and improve the mutual independence of modules

Principle of synthetic administration

Try to use association relations such as composition or aggregation first, and then consider using inheritance relations

Creator mode (5)

Singleton mode

This pattern involves a single class that is responsible for creating its own objects while ensuring that only a single object is created. This class provides a way to access its unique object, which can be accessed directly without instantiating the object of this class

Hungry man model

Each object is initialized before it is used. This can lead to potential performance problems: what if the object is large? Loading this object into memory without using it is a huge waste

public final class EagerSingleton {
    
    private static EagerSingleton singObj = new EagerSingleton();

    private EagerSingleton() {
    }

    public static EagerSingleton getSingleInstance() {
        return singObj;
    }
}

Lazy mode

It uses delayed loading to ensure that objects will not be initialized before they are used. However, usually at this time, the interviewer will ask new questions to make things difficult. He would ask: is this writing thread safe? The answer must be: unsafe

public final class LazySingleton {

    private static LazySingleton singObj = null;

    private LazySingleton() {
    }

    public static LazySingleton getSingleInstance() {
        if (null == singObj ) {
            singObj = new LazySingleton();
        }
        return singObj;
    }
}

Double checked lock

Double check lock mode is a very good singleton implementation mode, which solves the problems of singleton, performance and thread safety. In addition, volatile keyword is added to prevent instruction rearrangement at the bottom of JVM, which perfectly realizes the singleton mode

public final class DoubleCheckedSingleton {
	//volatile prevents JVM instruction rearrangement
    private static volatile DoubleCheckedSingletonsingObj = null;

    private DoubleCheckedSingleton() {
    }

	// Provide a static method to get the object
    public static DoubleCheckedSingleton getSingleInstance() {
        // For the first time, if instance is not null, it will not enter the lock grabbing phase and return the instance directly
        if (null == singObj ) {
            Synchronized(DoubleCheckedSingleton.class) {
                // After grabbing the lock, judge whether it is null again
                if (null == singObj) {
                    singObj = new DoubleCheckedSingleton();
                }
            }
        }
        return singObj;
    }
}

Static inner class

Here we will propose a new mode - Initialization on Demand Holder This method uses internal classes to delay loading objects. When initializing this internal class, JLS(Java Language Sepcification) will ensure the thread safety of this class. The greatest beauty of this writing method is that it completely uses the mechanism of Java virtual machine to ensure synchronization, and there is no synchronization keyword

public class Singleton {
	// Private construction method
	private Singleton(){}

    // Define a static inner class
    private static class SingletonHolder {
        public final static Singleton instance = new Singleton();
    }

    // Provide public access
    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }
}

The instance will not be initialized when the Singleton class is loaded for the first time. Only getInstance is called for the first time. The virtual machine loads the SingletonHolder and initializes the instance, which can not only ensure thread safety, but also ensure the uniqueness of the Singleton class

Static inner class singleton mode is an excellent singleton mode, which is commonly used in open source projects. Without any locking, it ensures the safety of multithreading without any performance impact and waste of space

Enumeration mode

The enumeration class implementation singleton mode is highly recommended because the enumeration type is thread safe and can only be loaded once. The designer makes full use of the particularity of enumeration to implement the singleton mode. The writing method of enumeration is very simple, and the enumeration type is the only singleton implementation mode that will not be destroyed

public enum Singleton{
	INSTANCE;
}

Destroy singleton mode

There are two ways to break the singleton pattern: serialization and reflection

Serialization breaks the singleton pattern

// Serialization breaking singleton mode for testing
public class Test {

    public static void main(String[] args) throws Exception {
        writeObject2File();

        // The object addresses read twice are different, which successfully destroys the singleton mode
        readObjectFromFile();
        readObjectFromFile();
    }

    // Read data from file (object)
    public static void readObjectFromFile() throws Exception {
        // Create an input stream object
        ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream("file path"));
        // Read objects in file
        Singleton instance = (Singleton)ois.readObject();
        System.out.println(instance);
        // Release resources
        ois.close();
    }

    // Write data (objects) to a file
    public static void writeObject2File() throws Exception {
        // Get Singleton object
        Singleton instance = new Singleton();
        // Create object output stream object
        ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream("file path"));
        // Write object to file
        oos.writeObject(instance);
        // Release resources
        oos.close();
    }
}

terms of settlement
Add the readResolve() method to the Singleton class. It is called by reflection during deserialization. If this method is defined, it returns the value of this method. If it is not defined, it returns the new object

public class Singleton {
    // Private construction method
    private Singleton(){}

    // Define a static inner class
    private static class SingletonHolder {
        public final static Singleton instance = new Singleton();
    }

    // Provide public access
    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }

    // When deserializing, the method will be called automatically and the return value of the method will be returned directly
    public Object readResolve(){
        return SingletonHolder.instance;
    }
}

The reflection mode destroys the singleton mode

// Reflection destruction singleton mode for test
public class Test2 {
    public static void main(String[] args) {
        // Gets the bytecode object of Singleton
        Class<Singleton> clazz = Singleton.class;
        // Get parameterless constructor
        Constructor cons = clazz.getDeclaredConstructor();
        // Cancel access check
        cons.setAccessible(true);
        // create object
        Singleton s1 = (Singleton)cons.newInstance();
        Singleton s2 = (Singleton)cons.newInstance();
        // The output is false, indicating that the singleton mode is broken
        System.out.println(s1 == s2);
    }
}

terms of settlement
Add judgment in the constructor. If the constructor is called multiple times to create an object, a runtime exception will be thrown

public class Singleton {

    private static boolean flag = false;

    // Private construction method
    private Singleton(){
        synchronized (Singleton.class){
            // Judge whether the value of flag is true
            // true indicates a non first access
            // false indicates the first visit
            if(flag){
                throw new RuntimeException("Cannot create multiple objects");
            }
            // Set flag to true
            flag = true;
        }
    }

    // Define a static inner class
    private static class SingletonHolder {
        public final static Singleton instance = new Singleton();
    }

    // Provide public access
    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }

    // When deserializing, the method will be called automatically and the return value of the method will be returned directly
    public Object readResolve(){
        return SingletonHolder.instance;
    }
}

JDK source code analysis - Runtime

The Runtime class in the JDK source code is the singleton pattern used

Prototype mode

summary
Using an instance that has been created as a prototype, create a new object that is the same as the prototype object by copying the prototype object

structure
Abstract prototype class: Specifies the clone method that the concrete prototype object must implement
Concrete prototype class: the clone method that implements the Abstract prototype class, which is an object that can be copied
Access class: use the clone method in the concrete prototype class to copy the new object

The cloning of prototype pattern can be divided into shallow cloning and deep cloning
Shallow cloning: create a new object. The properties of the new object are exactly the same as those of the original object. For non basic type properties, it still points to the memory address of the object pointed to by the original property
Deep clone: when a new object is created, other objects referenced in the attribute will also be cloned and no longer point to the original object address

The clone method provided in the Object class in java is used to realize shallow cloning. Clonable interface is the Abstract prototype class in the above class diagram, and the sub implementation class implementing clonebale interface is the specific prototype class

Code example

// Concrete prototype class
public class Realizetype implements Cloneable{
    public Realizetype(){
        System.out.println("The specific prototype object is created");
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        System.out.println("The specific prototype was copied successfully");
        return super.clone();
    }
}

//test
public class Client {
    public static void main(String[] args) throws Exception{
        //Create a prototype class object
        Realizetype realizetype = new Realizetype();
        //Clone the object by calling the clone method in the Realizetype class
        Realizetype clone = (Realizetype)realizetype.clone();

        System.out.println(realizetype==clone);
    }
}

case
Using prototype mode to generate "three good students" award certificate

//Certificate of merit
public class Citation implements Cloneable{
    //Names on three good students
    private String name;
    public String getName(){
        return name;
    }
    public void setName(String name){
        this.name = name;
    }

    public void show(){
        System.out.println(name+"Classmate: get a certificate");
    }

    @Override
    protected Citation clone() throws CloneNotSupportedException {
        return (Citation)super.clone();
    }
}

//test
public class Client {
    public static void main(String[] args) throws Exception{
        //Create a prototype class object
        Citation citation = new Citation();
        //Clone award object
        Citation clone = (Citation)citation.clone();

        citation.setName("zhangsan");
        clone.setName("lisi");

        citation.show();
        clone.show();
    }
}

Prototype mode usage scenario

  • The creation of objects is very complex. You can quickly create objects using prototype mode
  • High performance and safety requirements

Extended (deep clone)

Deep cloning using object flow

//Student class, in order to show deep cloning
public class Student implements Serializable {
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                '}';
    }
}

//Certificate of merit
public class Citation implements Cloneable, Serializable {
    //Names on three good students
    private Student student;
    public Student getStudent() {
        return student;
    }
    public void setStudent(Student student) {
        this.student = student;
    }
    public void show(){
        System.out.println(student.getName()+"Classmate: get a certificate");
    }
    @Override
    protected Citation clone() throws CloneNotSupportedException {
        return (Citation)super.clone();
    }
}

//test
public class Client {
    public static void main(String[] args) throws Exception{
        //Create a prototype class object
        Citation citation = new Citation();
        Student student = new Student();
        student.setName("zhangsan");
        citation.setStudent(student);

        //Create a stream output stream object for the object
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:/test.txt"));
        //Write object to file
        oos.writeObject(citation);
        //Release resources
        oos.close();

        //Create an input stream object for the object
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:/test.txt"));
        //Read object from file
        Citation citation1 = (Citation)ois.readObject();
        //Release resources
        ois.close();

        citation1.getStudent().setName("lisi");

        citation.show();
        citation1.show();
    }
}

Note: for deep cloning using object stream, the related bean s should implement the serialization interface

Factory method model

The biggest advantage of factory mode is decoupling

The unchanged sample code used in the following factory methods

// Coffee
public abstract class Coffee {
    // Add sugar
    public void addSuger(){
        System.out.println("Add sugar");
    }
    // Add milk
    public void addMilk(){
        System.out.println("Add milk");
    }
    // Coffee name
    public abstract String getName();
}

//Cafe Latte
public class LatteCoffee extends Coffee{
    // name
    @Override
    public String getName() {
        return "Cafe Latte";
    }
}

//Cafe Americano
public class AmericanCoffee extends Coffee{
    // name
    @Override
    public String getName() {
        return "Cafe Americano";
    }
}

Simple factory mode

Abstract product: it defines the product specification and describes the main characteristics and functions of the product
Concrete products: subclasses that implement or inherit Abstract products
Specific factory: provides a method to create a product, through which the caller obtains the product

Code example

// Simple coffee factory, producing coffee
public class SimpleCoffeeFactory {
    public Coffee createCoffee(String type){
        //Declare variables of Coffee type and create different Coffee subclass objects according to different types
        Coffee coffee = null;
        if ("american".equals(type)) {
            coffee = new AmericanCoffee();
        } else if ("latte".equals(type)){
            coffee = new LatteCoffee();
        } else {
            throw new RuntimeException("No,");
        }
        return coffee;
    }
}

// coffee shop
public class CoffeeStore {
    public Coffee orderCoffee(String type){
        // Create a simple factory
        SimpleCoffeeFactory simpleCoffeeFactory = new SimpleCoffeeFactory();
        // Call the factory to produce coffee
        Coffee coffee = simpleCoffeeFactory.createCoffee("latte");
        // Add ingredients
        coffee.addMilk();
        coffee.addSuger();
        return coffee;
    }
}

// Test class
public class Client {
    public static void main(String[] args) {
        //Create coffee shop class
        CoffeeStore coffeeStore = new CoffeeStore();
        //order
        Coffee coffee = coffeeStore.orderCoffee("latte");
        System.out.println(coffee.getName());
    }
}

advantage:
Encapsulates the process of creating objects, which can be obtained directly through parameters. Separate the object creation from the business logic layer, so as to avoid modifying the customer code in the future. If you want to implement a new product, you can directly modify the factory class without modifying it in the source code, which reduces the possibility of modifying the customer code and makes it easier to expand

Disadvantages:
When adding new products, you still need to modify the factory code, which violates the "opening and closing principle"

Expansion - static factory

// Simple coffee factory, producing coffee
public class SimpleCoffeeFactory {
    public static Coffee createCoffee(String type){
        //Declare variables of Coffee type and create different Coffee subclass objects according to different types
        Coffee coffee = null;
        if ("american".equals(type)) {
            coffee = new AmericanCoffee();
        } else if ("latte".equals(type)){
            coffee = new LatteCoffee();
        } else {
            throw new RuntimeException("No,");
        }
        return coffee;
    }
}

Factory method model

For the shortcomings of simple factory, the factory method mode can be perfectly solved and fully abide by the opening and closing principle

Concept:
Define an interface for creating objects, and let subclasses decide which product class object to instantiate. Factory methods delay instantiation of a product class to subclasses of its factory

Structure:
Abstract factory: it provides an interface for creating products, through which callers access factory methods of specific factories to create products
Concrete factory: it mainly realizes the abstract methods in the abstract factory and completes the creation of specific products
Abstract product: it defines the product specification and describes the main characteristics and functions of the product
Concrete product: it implements the interface defined by the abstract product role lock, which is created by the concrete factory and corresponds to the concrete factory one by one

Code example

//Abstract factory
public interface CoffeeFactory {
    //Method of creating coffee object
    Coffee createCoffee();
}

//American coffee factory, specializing in the production of American coffee
public class AmericanCoffeeFactory implements CoffeeFactory{
    @Override
    public Coffee createCoffee() {
        return new AmericanCoffee();
    }
}

//Latte coffee factory, specializing in the production of latte coffee
public class LatteCoffeeFactory implements CoffeeFactory{
    @Override
    public Coffee createCoffee() {
        return new LatteCoffee();
    }
}

// coffee shop
public class CoffeeStore {
    private CoffeeFactory factory;

    public void setFactory(CoffeeFactory factory){
        this.factory = factory;
    }

    public Coffee orderCoffee(){
        // Call the factory to produce coffee
        Coffee coffee = factory.createCoffee();
        // Add ingredients
        coffee.addMilk();
        coffee.addSuger();
        return coffee;
    }
}

//test
public class Client {
    public static void main(String[] args) {
        //Create coffee shop object
        CoffeeStore store = new CoffeeStore();
        //Create factory
        AmericanCoffeeFactory factory = new AmericanCoffeeFactory();
        store.setFactory(factory);
        //Order coffee
        Coffee coffee = store.orderCoffee();

        System.out.println(coffee.getName());
    }
}

advantage:
Users only need to know the name of the specific factory to get the required products, without knowing the specific creation process of the products
When a new product is added to the system, only the specific product category and the corresponding specific factory category need to be added without any modification to the original factory, which meets the opening and closing principle

Disadvantages:
Every time a product is added, a specific product class and a corresponding specific factory class are added, which increases the complexity of the system

Abstract factory pattern

Concept:
It is a pattern structure that provides an interface for an access class to create a group of related or interdependent objects, and the access class can obtain different products of the same family without specifying the specific class of the product
Abstract factory mode is an upgraded version of factory method mode. Factory method mode only produces one level of products, while abstract factory mode can produce multiple levels of products

Structure:
Abstract factory: it provides an interface for creating products. It contains multiple methods for creating products, and can create multiple products of different levels
Concrete factory: it mainly implements multiple abstract methods in the abstract factory to complete the creation of specific products
Abstract product: it defines the product specification and describes the main characteristics and functions of the product. The abstract factory pattern has multiple Abstract products
Concrete product: it implements the interface defined by the abstract product role lock, which is created by the concrete factory. It has a many-to-one relationship with the concrete factory

Code example

//Dessert abstract class
public abstract class Dessert {
    public abstract void show();
}

//Tiramisu
public class Trimisu extends Dessert {
    @Override
    public void show() {
        System.out.println("Tiramisu");
    }
}

//Matcha Mousse
public class MatchaMousse extends Dessert{
    @Override
    public void show() {
        System.out.println("Matcha Mousse");
    }
}

//Abstract factory
public interface DessertFactory {
    //Produce coffee
    Coffee createCoffee();
    //Produce dessert
    Dessert createDessert();
}

//American dessert factory
public class AmericanDessertFactory implements DessertFactory {
    //Cafe Americano
    @Override
    public Coffee createCoffee() {
        return new AmericanCoffee();
    }
    //Matcha Mousse
    @Override
    public Dessert createDessert() {
        return new MatchaMousse();
    }
}

//Italian dessert factory
public class ItalyDessertFactory implements DessertFactory {
    //Cafe Latte
    @Override
    public Coffee createCoffee() {
        return new LatteCoffee();
    }
    //Tiramisu
    @Override
    public Dessert createDessert() {
        return new Trimisu();
    }
}

//test
public class client {
    public static void main(String[] args) {
        //Created is an Italian dessert factory object
        ItalyDessertFactory factory = new ItalyDessertFactory();
        //Get latte and tiramisu
        Coffee coffee = factory.createCoffee();
        Dessert dessert = factory.createDessert();

        System.out.println(coffee.getName());
        dessert.show();
    }
}

advantage
When multiple objects in a product family are designed to work together, it can ensure that the client always uses only the objects in the same product family

shortcoming
When a new product needs to be added to the product family, all factory classes need to be modified

Mode extension

Simple factory + profile decoupling
Factory objects and products can be decoupled through factory mode + configuration file. Load the full class name in the configuration file in the factory class and create an object for storage. If the client needs an object, it can get it directly

Step 1: define the configuration file
For the convenience of demonstration, we use the properties file as the configuration file, named bean properties

american=com.hupf.study.demo1.AmericanCoffee
latte=com.hupf.study.demo1.LatteCoffee

Step 2: improve the factory class

public class CoffeeFactory {
    //Load the configuration file, obtain the full class name configured in the configuration file, and create the object of this class for storage
    //Define container objects to store coffee objects
    private static Map<String, Coffee> map = new HashMap();

    static{
        //Load profile
        Properties p = new Properties();
        InputStream is = CoffeeFactory.class.getClassLoader().getResourceAsStream("bean.properties");

        try{
            p.load(is);
            // Traversing the properties collection object
            Set<Object> keys = p.keySet();
            for(Object key : keys){
                //Get value according to key (full class name)
                String className = p.getProperty((String) key);
                //Get bytecode object
                Class aClass = Class.forName(className);
                Coffee obj = (Coffee)aClass.newInstance();
                map.put((String)key,obj);
            }
        } catch (Exception e){
            e.printStackTrace();
        }
    }

    //Get object by name
    public static Coffee createCoffee(String name){
        return map.get(name);
    }
}

//test
public class client {
    public static void main(String[] args) {
        Coffee american = CoffeeFactory.createCoffee("american");
        System.out.println(american.getName());
    }
}

The static member variable is used to store the created object (the key stores the name and the value stores the corresponding object), while reading the configuration file and creating the object are written in the static code block, so that it only needs to be executed once

JDK source code analysis - collection Iterator method

Builder pattern

summary
Separate the construction and representation of a complex object, so that the same construction process can create different representations

  • It separates the construction of components (in the charge of Builder) and assembly (in the charge of Director). Thus, complex objects can be constructed. This pattern is applicable to the complex construction process of an object
  • The decoupling of construction and assembly is realized. Different builders and the same assembly can also make different objects; From the same construction, different assembly sequences can also make different objects. That is to realize the decoupling of construction algorithm and assembly algorithm, and realize better reuse
  • The builder pattern can separate the component from its assembly process and create a complex object step by step. Users only need to specify the type of complex object to get the object without knowing its internal specific construction details

structure
Abstract Builder class: this interface specifies the creation of those parts of complex objects, and does not involve the creation of specific object parts
Concrete Builder class: it implements the Builder interface and completes the specific creation method of various components of complex products. After the reconstruction process, provide an example of the product
Product: complex object to create
Director: call the specific builder to create each part of the complex object. The commander does not involve the information of specific products, but is only responsible for ensuring that each part of the object is created completely or in a certain order

Create a bike, code example

// Product category, bicycle
public class Bike {
    private String frame;//Frame
    private String seat;//Car seat
    public String getFrame() {
        return frame;
    }
    public void setFrame(String frame) {
        this.frame = frame;
    }
    public String getSeat() {
        return seat;
    }
    public void setSeat(String seat) {
        this.seat = seat;
    }
    @Override
    public String toString() {
        return "Bike{" +
                "frame='" + frame + '\'' +
                ", seat='" + seat + '\'' +
                '}';
    }
}

//Specific Builder: build worship bike
public class MobileBuilder extends Builder {
    @Override
    public void buildFrame() {
        bike.setFrame("Carbon fiber frame");
    }
    @Override
    public void buildSeat() {
        bike.setSeat("Leather seat");
    }
    @Override
    public Bike createBike() {
        return bike;
    }
}

//Specific Builder: build ofo
public class OfoBuilder extends Builder{
    @Override
    public void buildFrame() {
        bike.setFrame("Aluminum alloy frame");
    }
    @Override
    public void buildSeat() {
        bike.setSeat("Rubber seat");
    }
    @Override
    public Bike createBike() {
        return bike;
    }
}

//Abstract builder
public abstract class Builder {
    //Declare a variable of type Bike and assign a value
    protected Bike bike = new Bike();
    //Build frame
    public abstract void buildFrame();
    //Building a seat
    public abstract void buildSeat();
    //Build a bike
    public abstract Bike createBike();
}

//Commander
public class Director {
    //Declare variables of type builder
    private Builder builder;
    public Director(Builder builder){
        this.builder=builder;
    }
    //Method of assembling bicycle
    public Bike construct(){
        builder.buildFrame();
        builder.buildSeat();
        return builder.createBike();
    }
}

//test
public class Client {
    public static void main(String[] args) {
        //Create commander object
        Director director = new Director(new MobileBuilder());
        //Let the conductor direct the assembly of bicycles
        Bike bike = director.construct();

        System.out.println(bike.getFrame());
        System.out.println(bike.getSeat());
    }
}

be careful
It can simplify the system structure and combine the commander class with the abstract builder

//Abstract builder
public abstract class Builder {
    //Declare a variable of type Bike and assign a value
    protected Bike bike = new Bike();
    //Build frame
    public abstract void buildFrame();
    //Building a seat
    public abstract void buildSeat();
    //Build a bike
    public abstract Bike createBike();
    //Method of assembling bicycle
    public Bike construct(){
        builder.buildFrame();
        builder.buildSeat();
        return builder.createBike();
    }
}

explain
This does simplify the system construction, but at the same time, it also increases the responsibilities of the abstract builder class, which is not in line with the principle of single responsibility. If the commander's assembly method is too complex, it is recommended to package it into the commander

advantage

  • The builder model has good encapsulation. Using the builder mode can effectively encapsulate changes. In the scenario of using the builder mode, the general product class and builder class are relatively stable. Therefore, encapsulating the main business logic in the commander can achieve better stability as a whole
  • In the builder mode, the client does not need to know the details of the internal composition of the product, decouples the product itself from the product creation process, so that the same creation process can create different product objects
  • You can more finely control the creation process. The complex creation steps are decomposed into different methods, which makes the creation process clearer and easier to use programs to control the creation process
  • The builder pattern is easy to extend. If the new requirements can be completed by implementing a new builder class, there is basically no need to modify the tested code, so there will be no risk to the original functions. Comply with the opening and closing principle

shortcoming
The products created by the builder mode generally have more in common and their components are similar. If there are great differences between products, the builder mode is not suitable for use, so its scope of use is limited

Usage scenario
The Builder pattern creates complex objects. Each part of its product is often faced with drastic changes, but the algorithm that combines them is relatively stable, so it is usually used in the following occasions

  • The created object is complex and consists of multiple components. Each component has complex changes, but the earlier order between components is stable
  • The algorithm of creating a complex object is independent of the components of the object and their assembly method, that is, the construction process and final representation of the product are independent

Mode extension

When a class constructor needs to pass in many parameters, if an instance of this class is created, the code readability will be very poor, and it is easy to introduce errors. At this time, the builder pattern can be used for reconstruction

public class Phone {
    private String cpu;
    private String screen;
    private String memory;
    private String mainborad;

    private Phone(Builder builder) {
        this.cpu=builder.cpu;
        this.screen=builder.screen;
        this.memory=builder.memory;
        this.mainborad=builder.mainborad;
    }

    public static final class Builder{
        private String cpu;
        private String screen;
        private String memory;
        private String mainborad;
        public Builder cpu(String cpu){
            this.cpu = cpu;
            return this;
        }
        public Builder screen(String screen){
            this.screen = screen;
            return this;
        }
        public Builder memory(String memory){
            this.memory = memory;
            return this;
        }
        public Builder mainborad(String mainborad){
            this.mainborad = mainborad;
            return this;
        }
        //Create a phone object using the builder
        public Phone build(){
            return new Phone(this);
        }
    }

    @Override
    public String toString() {
        return "Phone{" +
                "cpu='" + cpu + '\'' +
                ", screen='" + screen + '\'' +
                ", memory='" + memory + '\'' +
                ", mainborad='" + mainborad + '\'' +
                '}';
    }
}

//test
public class Client {
    public static void main(String[] args) {
        Phone phone = new Phone.Builder()
                .cpu("cpu")
                .mainborad("mainborad")
                .memory("memory")
                .screen("screen")
                .build();
        System.out.println(phone.toString());
    }
}

Summary (creator mode comparison)

Factory method mode VS builder mode

The factory method pattern focuses on the creation of the overall object; The builder model focuses on the process of component construction, which is intended to create a complex object through step-by-step precise construction
Let's give a simple example to illustrate the difference between the two. If we make a superman, if we use the factory method mode, we will directly produce a Superman with infinite power, capable of flying and wearing underwear; If you use the builder mode, you need to assemble the hands, feet, trunk and other parts, and then wear your underwear outside, so a superman was born

Abstract factory pattern VS builder pattern

Abstract factory can realize the creation of product family. A product family is a series of products: product combinations with different classification dimensions. Adopting abstract factory mode does not need to care about the construction process, but only about what products are produced by what factory
The builder model requires the product to be built according to the specified blueprint. His main purpose is to produce a new product by assembling spare parts
If the abstract factory pattern is regarded as an auto parts production factory to produce the products of a product family, the builder pattern is an auto assembly factory, which can return a complete car through the assembly of parts

Structural mode (7 kinds)

Structural pattern describes how to form a larger structure of classes or objects according to a certain layout. It is divided into class structural pattern and object structural pattern. The former uses inheritance mechanism to organize interfaces and classes, and the latter uses composition or aggregation to form objects

Because the coupling degree of combination relationship or aggregation relationship is lower than that of inheritance relationship and meets the "composition principle", the object structural pattern has more flexibility than the class structural pattern

The structural mode is divided into the following 7 types:

  • proxy pattern
  • Adapter mode
  • Decorative pattern
  • Bridging mode
  • Appearance mode
  • Combination mode
  • Sharing element mode

proxy pattern

summary
For some reason, an object needs to be provided with a proxy to control access to the object. At this time, the access object is not suitable or can not directly reference the target object. The proxy object acts as an intermediary between the access object and the target object

According to the different generation time of proxy class, proxy in java is divided into static proxy and dynamic proxy.
Static proxy classes are generated at compile time
Dynamic proxy classes are dynamically generated by Java runtime. Dynamic agents include JDK agent and CGLib agent

structure
Abstract topic class (Subject): declare the business methods implemented by real topics and proxy objects through interfaces or abstract classes
Real Subject: it realizes the specific business in the abstract subject. The real object represented by the time management object is the object to be referenced finally
proxy class: it provides the same interface as the real topic. It contains references to the real topic. It can access, control or extend the functions of the real topic

Static proxy

Examples of ticket sales at railway stations

//Interface for selling train tickets
public interface SellTickets {
    public void sell();
}

//Train station ticket
public class TrainStation implements SellTickets{
    @Override
    public void sell() {
        System.out.println("Train station ticket");
    }
}

//Ticket selling point
public class ProxyPoint implements SellTickets{
    //Declare railway station class object
    private TrainStation trainStation=new TrainStation();

    @Override
    public void sell() {
        System.out.println("Service fees are charged at the consignment point");
        trainStation.sell();
    }
}

public class Client {
    public static void main(String[] args) {
        //Create consignment point class object
        ProxyPoint proxyPoint = new ProxyPoint();
        //Selling tickets
        proxyPoint.sell();
    }
}

JDK dynamic agent

Java provides a dynamic proxy class proxy. Proxy is not the class of proxy object mentioned above, but provides a static method (newProxyInstance method) to create an object to obtain the proxy object

//Interface for selling train tickets
public interface SellTickets {
    public void sell();
}

//Train station ticket
public class TrainStation implements SellTickets{
    @Override
    public void sell() {
        System.out.println("Train station ticket");
    }
}

//Gets the factory class of the proxy object
public class ProxyFactory {
    //Declare target object
    private TrainStation station = new TrainStation();
    //Method to get proxy object
    public SellTickets getProxyObject(){
        //Return proxy object
        /**
         * classLoader:Class loader, which is used to load proxy classes. The class loader can be obtained through the target object
         * Class<?>[] interfaces:Bytecode object of the interface implemented by the proxy class
         * InvocationHandler:Call handler for proxy object
         */
        SellTickets proxyObject = (SellTickets)Proxy.newProxyInstance(
                station.getClass().getClassLoader(),
                station.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**
                     *
                     * @param proxy: Proxy object. It is the same object as proxyObject object and is basically not used in the invoke method
                     * @param method: method object that encapsulates the methods in the interface
                     * @param args: Actual parameters of the calling method
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //Agent class enhancements
                        System.out.println("Service charge for agency");
                        //Method of executing target object
                        Object invoke = method.invoke(station, args);
                        return invoke;
                    }
                }
        );
        return proxyObject;
    }
}

//test
public class Client {
    public static void main(String[] args) {
        //Get proxy object
        //Create proxy factory object
        ProxyFactory proxyFactory = new ProxyFactory();
        //Get the proxy object using the method of the factory object
        SellTickets sellTickets = proxyFactory.getProxyObject();
        //Calling real object methods
        sellTickets.sell();
    }
}

Execution process

  1. Call the sell method through the proxy object in the test class
  2. According to the characteristics of polymorphism, the sell method in the Proxy class $Proxy is executed
  3. The sell method in the Proxy class $Proxy calls the invoke method of the subclass object of the InvocationHandler interface
  4. The invoke method executes the sell method in the class to which the real object belongs through reflection

CGLib dynamic proxy

CGLib is a powerful and high-performance code generation package. It provides agents for classes that do not implement interfaces and provides a good supplement to the dynamic agent of JDk

CGLib is a package provided by a third party, so you need to introduce the coordinates of the jar package

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>

Code example

//Train station ticket
public class TrainStation{
    public void sell() {
        System.out.println("Train station ticket");
    }
}

//Proxy object factory, which is used to obtain proxy objects
public class ProxyFactory implements MethodInterceptor {
    //Real object
    private TrainStation trainStation = new TrainStation();

    public TrainStation getProxyObject(){
        //Create an Enhancer object, similar to the Proxy class in JDK Proxy
        Enhancer enhancer = new Enhancer();
        //Sets the bytecode object of the parent class
        enhancer.setSuperclass(TrainStation.class);
        //Set callback function
        enhancer.setCallback(this);
        //Create proxy object
        TrainStation proxyObject = (TrainStation)enhancer.create();
        return proxyObject;
    }


    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        //Call the method of the target object
        System.out.println("Service fees are charged at the consignment point");
        trainStation.sell();
        return null;
    }
}

//test
public class Client {
    public static void main(String[] args) {
        //Create proxy factory object
        ProxyFactory proxyFactory = new ProxyFactory();
        //Get proxy object
        TrainStation proxyObject = proxyFactory.getProxyObject();
        //Call the sell method in the proxy object to sell tickets
        proxyObject.sell();
    }
}

Comparison of three agents

JDK agent and CGLib agent

CGLib is used to implement dynamic proxy. The bottom layer of CGLib uses ASM bytecode generation framework and bytecode technology to generate proxy classes. The only thing to note is that CGLib cannot proxy classes or methods whose life is final, because CGLib principle is to dynamically generate subclasses of the proxy class

In jdk1 8 after gradually optimizing the JDK dynamic agent, the efficiency of JDK agent is higher than that of CGLib agent under the condition of less calls. Therefore, if there is an interface, use JDK dynamic proxy, and if there is no interface, use CGLib proxy

Dynamic agent and static agent

Compared with static proxy, the biggest advantage of dynamic proxy is that all the methods declared in the interface are transferred to a centralized method of the calling processor (InvocationHandler). In this way, when there are a large number of interface methods, we can handle them flexibly without transferring each method like a static agent

If a method is added to the interface, all proxy classes need to implement this method in addition to all implementation classes in the static proxy mode. It increases the complexity of code maintenance. Dynamic agents do not have such problems

Advantages of agent mode

  • The proxy pattern acts as a mediator between the client and the target object and protects the target object
  • The proxy object can extend the function of the target object
  • The proxy mode can separate the client from the target object and reduce the coupling of the system to a certain extent

Disadvantages of agent mode

It increases the complexity of the system

Usage scenario

Remote agent
Local services request remote services over the network. In order to realize local to remote communication, we need to realize network communication and deal with possible exceptions. For good code design and maintainability, we hide the network communication part and expose only one interface to the local service. Through this interface, we can access the functions of the remote service without paying too much attention to the communication part code

Firewall agent
When you configure your browser to use the proxy function, the firewall will transfer your browser's request to the Internet; When the Internet returns a response, the proxy server forwards it to your browser

Protection agent
Control access to an object. If necessary, different users can be provided with different levels of permissions

Adapter mode

Many examples in life, such as chargers and card readers, can be abstracted into adapters

Convert the interface of a class into another interface that the customer wants, so that the classes that cannot work together due to incompatible interfaces can work together

The adapter mode is divided into class adapter mode and object adapter mode. The former has higher coupling between classes than the latter, and requires programmers to understand the internal structure of relevant components in the existing component library, so there are relatively few applications

structure
Target interface: the interface expected by the current system business, which can make the abstract class interface
Adapter class (Adapee): it is the component interface in the existing component library accessed and adapted
Adapter class: it is a converter that converts the adapter interface into the target interface by inheriting or referring to the adapter object, so that the client can access the adapter in the format of the target interface

Class adapter mode

Reader code example

//Adapter interface
public interface TFCard {
    //Read data from TF Card
    public String readTF();
    //Write data to TF Card
    public void writeTF(String string);
}
//Adapter class
public class TFCardImpl implements TFCard{
    @Override
    public String readTF() {
        String msg = "TF Card read msg:hello world";
        return msg;
    }
    @Override
    public void writeTF(String string) {
        System.out.println("write : "+string);
    }
}

//Target interface
public interface SDCard {
    //Read data from SD card
    public String readSD();
    //Write data to SD card
    public void writeSD(String string);
}
//Target class
public class SDCardImpl implements SDCard{
    @Override
    public String readSD() {
        String msg = "SD Card read msg:hello world";
        return null;
    }

    @Override
    public void writeSD(String string) {
        System.out.println("write : "+string);
    }
}

//Computer class
public class Computer {
    //Read data from SD card
    public String readSD(SDCard sdCard){
        if(sdCard==null){
            throw new NullPointerException("Cannot be null");
        }
        return sdCard.readSD();
    }
}

//adapter class 
public class SDAdapter extends TFCardImpl implements SDCard{
    @Override
    public String readSD() {
        System.out.println("adapter read tf card");
        return readTF();
    }

    @Override
    public void writeSD(String string) {
        System.out.println("adapter write tf card");
        writeTF(string);
    }
}

//test
public class Client {
    public static void main(String[] args) {
        //Create computer object
        Computer computer = new Computer();
        //Read data from SD card
        String msg = computer.readSD(new SDAdapter());
        System.out.println(msg);
    }
}

The class adapter pattern violates the principle of composite reuse. The class adapter makes the client class available when there is an interface specification, otherwise it is not available

Object Adapter Pattern

Modify the adapter class in the above example to no longer inherit the adapter, but rely on the adapter

//adapter class 
public class SDAdapterTF implements SDCard{
    //Declare adapter class
    private TFCard tfCard;
    public SDAdapterTF(TFCard tfCard) {
        this.tfCard = tfCard;
    }
    @Override
    public String readSD() {
        System.out.println("adapter read tf card");
        return tfCard.readTF();
    }
    @Override
    public void writeSD(String string) {
        System.out.println("adapter write tf card");
        tfCard.writeTF(string);
    }
}

Note: another adapter mode is the interface adapter mode. When you do not want to implement all the methods in an interface, you can create an abstract class Adpater to implement all the methods. At this time, we only need to inherit the abstract class

Application scenario

  • The previously developed system has classes that meet the functional requirements of the new system, but their interfaces are inconsistent with those of the new system
  • Use the components provided by the third party, but the component interface definition is different from the interface definition you need

JDK source code analysis

The adaptation of Reader (character stream) and InputStream (byte stream) uses InputStreamReader and OutputStreamWriter

InputStreamReader and OutputStreamWriter inherit from Java The Reader and writer in the IO package implement the abstract and unimplemented methods in them

By analyzing the source code, we can know that InputStreamReader does the conversion from InputStream byte stream class to Reader character stream. The design and implementation of the streetdecoder actually adopts the adapter pattern

Decorator mode

The mode of dynamically adding some responsibilities to the object without changing the existing object structure, that is, adding its additional functions

structure
Abstract build role (Component): define an abstract interface to standardize objects ready to receive additional responsibilities
Concrete Component: implement abstract components and add some responsibilities to them by decorating roles
Abstract Decorator: inherits or implements abstract components and contains instances of specific components. The functions of specific components can be extended through their subclasses
Concrete decorator: implement the relevant methods of abstract decoration and add additional responsibilities to the concrete construction object

Fast food restaurant code example

//Fast food class (Abstract build role)
public abstract class FastFood {
    private float price;//Price
    private String desc;//describe
    public FastFood(float price, String desc) {
        this.price = price;
        this.desc = desc;
    }
    public float getPrice() {
        return price;
    }
    public void setPrice(float price) {
        this.price = price;
    }
    public String getDesc() {
        return desc;
    }
    public void setDesc(String desc) {
        this.desc = desc;
    }
    public abstract float cost();
}

//Fried rice (specific component role)
public class FriedRice extends FastFood{
    public FriedRice() {
        super(10,"Fried rice");
    }
    @Override
    public float cost() {
        return getPrice();
    }
}

//Fried noodles (specific component role)
public class FriedNoodles extends FastFood{
    public FriedNoodles() {
        super(12,"Stir-Fried Noodles with Vegetables");
    }
    @Override
    public float cost() {
        return getPrice();
    }
}

//Decoration class (abstract decoration role)
public abstract class Garnish extends FastFood{
    public Garnish(FastFood fastFood,float price, String desc) {
        super(price, desc);
        this.fastFood=fastFood;
    }
    //Declare variables for fast food classes
    private FastFood fastFood;
    public FastFood getFastFood() {
        return fastFood;
    }
    public void setFastFood(FastFood fastFood) {
        this.fastFood = fastFood;
    }
}

//Eggs (specific decorators)
public class Egg extends Garnish{
    public Egg(FastFood fastFood) {
        super(fastFood, 1, "egg");
    }
    @Override
    public float cost() {
        //Calculate price
        return getPrice()+getFastFood().cost();
    }
    @Override
    public String getDesc() {
        return super.getDesc()+getFastFood().getDesc();
    }
}

//Bacon (specific decorator)
public class Bacon extends Garnish{
    public Bacon(FastFood fastFood) {
        super(fastFood, 2, "Bacon");
    }
    @Override
    public float cost() {
        //Calculate price
        return getPrice()+getFastFood().cost();
    }
    @Override
    public String getDesc() {
        return super.getDesc()+getFastFood().getDesc();
    }
}

public class Client {
    public static void main(String[] args) {
        //Order a fried rice
        FastFood food = new FriedRice();
        //Add eggs
        food = new Egg(food);
        //Add eggs
        food = new Egg(food);

        System.out.println(food.getDesc()+" "+food.getPrice());
    }
}

advantage

  • Decorator mode can bring more flexible extension functions than inheritance, and it is more convenient to use. It can obtain diversified results with different behavior states by combining different decorating objects. Decorator mode has better expansibility than inheritance, and perfectly follows the opening and closing principle. Inheritance is a static additional responsibility, while decoration is a dynamic additional responsibility
  • Decorative classes and decorated classes can develop independently without coupling each other. Decorative pattern is an alternative pattern of inheritance. Decorative pattern can dynamically expand the functions of an implementation class

Usage scenario

  • When the system cannot be extended by inheritance, or inheritance is not conducive to the expansion and maintenance of the system
  • Add responsibilities to a single object in a dynamic and transparent manner without affecting other objects
  • When the function requirements of the object can be added dynamically or revoked dynamically

JDK source code analysis
The wrapper class in the IO stream uses decorator mode. BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter

BufferedWriter uses decorator mode to enhance the implementation class of writer subclass and add buffer to improve the efficiency of writing data

Difference between agent and decorator

Differences between static proxy and decorator patterns
Similarities:
All of them should implement the same business interface as the target class
Declare the target object in both classes
Can enhance the target method without modifying the target class
difference:
The purpose is different. When decorating, it is to enhance the target object; In order to protect and hide the target object during static proxy
The construction place of the target object is different. The decorator is passed in from the outside and can be passed through the construction method; A static proxy is created inside the proxy to hide the target object

Bridging mode

Separate abstraction from implementation so that they can change independently. It is realized by replacing the inheritance relationship with the combination relationship, which reduces the coupling between the two variable dimensions of abstraction and implementation

structure
Abstraction: defines an abstract role and contains a reference to an implementation object
Refined Abstraction: it is a subclass of the abstract role, which implements the business methods in the parent class and calls the business methods in the abstract role through the composition relationship
Implementer: defines the interface to implement the role, and extends the abstract role calls
Concrete implementer: gives the concrete implementation of the implementation role interface

Video player code example

//Video files, role-based
public interface VideoFile {
    //Decoding function
    void decode(String fileName);
}

//avi video file, specific role
public class AVIFile implements VideoFile{
    @Override
    public void decode(String fileName) {
        System.out.println("avi Video files:"+fileName);
    }
}

//rmvb video file, specific role
public class RMVBFile implements VideoFile{
    @Override
    public void decode(String fileName) {
        System.out.println("rmvb Video files:"+fileName);
    }
}

//Abstract operating system classes, abstract roles
public abstract class OperatingSystem {
    //Declare the videoFile variable
    protected VideoFile videoFile;
    public OperatingSystem(VideoFile videoFile) {
        this.videoFile = videoFile;
    }
    public abstract void play(String fileName);
}

//Extended abstract role (Mac OS)
public class Mac extends OperatingSystem {
    public Mac(VideoFile videoFile) {
        super(videoFile);
    }
    @Override
    public void play(String fileName) {
        videoFile.decode(fileName);
    }
}

//Extended abstract role (windows operating system)
public class Windows extends OperatingSystem {
    public Windows(VideoFile videoFile) {
        super(videoFile);
    }
    @Override
    public void play(String fileName) {
        videoFile.decode(fileName);
    }
}

//test
public class Client {
    public static void main(String[] args) {
        //Create mac system object
        OperatingSystem mac = new Mac(new AVIFile());
        //Play video files using the operating system
        mac.play("War wolf");
    }
}

advantage

  • The bridging mode improves the scalability of the system. If one of the two change dimensions is extended arbitrarily, there is no need to modify the original system. For example, if there is still a video file type wmv, we only need to define another interface that implements VideoFile, and other classes do not need to be changed
  • Make details transparent to customers

Usage scenario

  • When a class has two independently changing dimensions and both dimensions need to be extended
  • When a system does not want to use inheritance or the number of system classes increases sharply due to multi-level inheritance
  • When a system needs to add more flexibility between the abstract and concrete roles of components. Avoid establishing static inheritance relationship between the two levels. Through the bridge mode, they can establish an association relationship in the abstract level

Appearance mode

Also known as facade mode, it is a mode that can be accessed more easily by using multiple complex subsystems by providing a consistent interface. This mode has a unified interface to the outside, and the external application does not care about the specific details of the internal subsystem, which will greatly reduce the complexity of the application and provide the maintainability of the program.

Appearance pattern is a typical application of "Dimitri's law"

structure

  • Facade: provides a common interface for multiple subsystems
  • Subsystem role: it realizes some functions of the system, and customers can access it through appearance role

Code example

Intelligent appliance control

//Electric lamps
public class Light {
    //turn on the light
    public void on(){
        System.out.println("Turn on the light");
    }
    //Turn off the lights
    public void off(){
        System.out.println("Turn off the lights");
    }
}

//Television
public class TV {
    //turn on the light
    public void on(){
        System.out.println("Turn on the TV");
    }
    //Turn off the lights
    public void off(){
        System.out.println("Turn off the TV");
    }
}

//Air conditioning
public class AirCondition {
    //turn on the light
    public void on(){
        System.out.println("Turn on the air conditioner");
    }
    //Turn off the lights
    public void off(){
        System.out.println("Turn off the air conditioner");
    }
}

//Appearance class
public class SmartAppliancesFacade {
    //Aggregate electric lamp object, TV object and air conditioning object
    private Light light;
    private TV tv;
    private AirCondition airCondition;
    public SmartAppliancesFacade() {
        light = new Light();
        tv = new TV();
        airCondition = new AirCondition();
    }
    //Voice control
    public void say(String messsage){
        if (messsage.contains("open")){
            on();
        } else if(messsage.contains("close")){
            off();
        } else {
            System.out.println("I don't understand");
        }
    }
    //One piece open
    private void on(){
        light.on();
        tv.on();
        airCondition.on();
    }
    //One off
    private void off(){
        light.off();
        tv.off();
        airCondition.off();
    }
}

//test
public class Client {
    public static void main(String[] args) {
        //Creating smart audio objects
        SmartAppliancesFacade facade = new SmartAppliancesFacade();
        //Control appliances
        facade.say("Turn on the appliance");
        facade.say("Turn off appliances");
    }
}

advantage

  • The coupling between the subsystem and the client is reduced, so that the change of the subsystem will not affect the calling of his client class
  • It shields the subsystem components from customers, reduces the number of objects handled by customers, and makes the subsystem easier to use

shortcoming
It does not comply with the opening and closing principle, and the modification is very troublesome

Usage scenario

  • When building a hierarchical system, defining the entry point of each layer in the subsystem using appearance patterns can simplify the dependencies between subsystems
  • When there are many complex subsystems, the appearance pattern can design a simple interface for the system to access the outside world
  • When there is a great connection between the client and multiple subsystems, the hot topic appearance pattern can separate them, so as to provide the independence and portability of the subsystem

Combination mode

Also known as partial whole pattern, it is used to treat a group of similar objects as a single object. The combination mode combines objects according to the tree structure, which is used to represent the partial and overall levels. This type of design pattern is a structural pattern, which creates a tree structure of object groups

structure

Abstract root node (Component): defines the common methods and attributes of objects at all levels of the system, and some default behaviors and attributes can be defined in advance
Composite: define the behavior of branch nodes, store child nodes, and combine branch nodes and leaf nodes to form a tree structure
Leaf node: leaf node object, under which there are no branches, is the smallest unit of system level traversal

case

Software menu

//The menu component (Abstract root node) should inherit this class whether it is a menu or a menu item
public abstract class MenuComponent {
    //Name of menu component, level
    protected String name;
    protected int level;
    //Add submenu
    public void add(MenuComponent menuComponent){
        throw new UnsupportedOperationException();
    }
    //Delete submenu
    public void remove(MenuComponent menuComponent){
        throw new UnsupportedOperationException();
    }
    //Gets the specified submenu
    public MenuComponent getChild(int index){
        throw new UnsupportedOperationException();
    }
    //Gets the name of the menu or menu item
    public String getName(MenuComponent menuComponent){
        return name;
    }
    //Method for printing menu names (including submenus and submenu items)
    public abstract String print();
}

//Menu item class: leaf node
public class MenuItem extends MenuComponent{
    public MenuItem(String name,int level) {
        this.name = name;
        this.level=level;
    }
    @Override
    public String print() {
        //Print the name of the menu item
        for (int i =0;i<level;i++){
            System.out.println("--");
        }
        System.out.println(name);
        return null;
    }
}

//Menu class: belongs to branch node
public class Menu extends MenuComponent{
    // A menu can have multiple submenus or submenu items
    private List<MenuComponent> menuComponentList = new ArrayList<>();
    public Menu(String name,int level) {
        this.name = name;
        this.level=level;
    }

    @Override
    public void add(MenuComponent menuComponent){
        menuComponentList.add(menuComponent);
    }
    @Override
    public void remove(MenuComponent menuComponent){
        menuComponentList.remove(menuComponent);
    }
    @Override
    public MenuComponent getChild(int index){
        return menuComponentList.get(index);
    }
    @Override
    public String getName(MenuComponent menuComponent){
        return name;
    }
    @Override
    public String print() {
        //Print menu name
        for (int i =0;i<level;i++){
            System.out.println("--");
        }
        System.out.println(name);
        //Print the name of the submenu or submenu item
        for (MenuComponent component : menuComponentList) {
            component.print();
        }
        return null;
    }
}

//test
public class Client {
    public static void main(String[] args) {
        //Create menu tree
        MenuComponent menu1 = new Menu("Menu management",2);
        menu1.add(new MenuItem("Page access",3));
        menu1.add(new MenuItem("Show access",3));
        menu1.add(new MenuItem("Edit access",3));
        menu1.add(new MenuItem("Delete access",3));
        menu1.add(new MenuItem("New access",3));
        MenuComponent menu2 = new Menu("Authority management",2);
        menu2.add(new MenuItem("Page access",3));
        menu2.add(new MenuItem("Submit save",3));
        MenuComponent menu3 = new Menu("Role management",2);
        menu3.add(new MenuItem("Page access",3));
        menu3.add(new MenuItem("New role",3));
        menu3.add(new MenuItem("Modify role",3));
        //Create first level menu
        MenuComponent menu = new Menu("system management",1);
        menu.add(menu1);
        menu.add(menu2);
        menu.add(menu3);
        //Print menu name
        menu.print();
    }
}

Classification and advantages of combination patterns

When using composite mode, according to the definition form of abstract component class, we can divide the composite mode into transparent composite mode and safe composite mode

advantage

  • Composite mode can clearly define hierarchical complex objects and represent all or part of the hierarchy of objects. It allows the client to ignore the differences of hierarchy and facilitate the control of the whole hierarchy
  • The client can consistently use a composite structure or a single object in it, regardless of whether the single object or the whole composite structure is processed, which simplifies the client code
  • It is convenient to add new branch nodes and leaf nodes in the combination mode without any modification to the existing class library, which conforms to the "opening and closing principle"
  • The combination pattern provides a flexible solution for the object-oriented implementation of tree structure. Through the recursive combination of leaf nodes and branch nodes, complex tree structure can be formed, but the control of tree structure is very simple

Usage scenario

The combination mode is born in response to the tree structure, so the use scenario of the combination mode is where the tree structure appears. For example, file directory display, multi-level directory presentation and other tree structure data operations

Sharing element mode

Sharing technology is used to effectively support the reuse of a large number of fine-grained objects. By sharing existing objects, it can greatly reduce the number of objects to be created and avoid the overhead of a large number of similar objects, so as to improve the utilization of system resources

structure

There are two statuses in the meta mode:
Internal state, a shareable part that will not change with the change of environment
External state refers to the unshareable part that changes with the environment. The key to the implementation of shared meta mode is to distinguish the two states in the application and externalize the external state

Abstract meta role (Flyweight):
Usually, it is an interface or abstract class, which declares the public methods of the specific meta class in the abstract meta class. These methods can provide the internal data (internal state) of the meta object to the outside world, and can also set the external data (external state) through these methods
Concrete Flyweight:
It implements an abstract meta class, called meta object; Storage space is provided for internal states in specific meta classes. Usually, we can design specific meta classes in combination with the singleton pattern to provide unique meta objects for each specific meta class
Unsharable Flyweight:
Not all subclasses of the desired Abstract shared metaclass need to be shared. Subclasses that cannot be shared can be designed as non shared concrete shared metaclasses; When you need an object that does not share a specific meta class, you can create it directly by instantiation
Flyweight Factory role:
Responsible for creating and managing meta roles. When a customer object requests a meta object, the meta factory checks whether there are qualified meta objects in the system, and if so, provides them to the customer; If no materialization exists, a new meta object is created

Example

Tetris

//Abstract meta role
public abstract class AbstractBox {
    //Method of obtaining graphics
    public abstract String getShape();
    //Display graphics and colors
    public void display(String color){
        System.out.println("shape:"+getShape()+" color:"+color);
    }
}

//I graphics class, specific meta roles
public class IBox extends AbstractBox{
    @Override
    public String getShape() {
        return "I";
    }
}

//L graphics class, specific meta role
public class LBox extends AbstractBox{
    @Override
    public String getShape() {
        return "L";
    }
}

//O graphic class, specific meta role
public class OBox extends AbstractBox{
    @Override
    public String getShape() {
        return "O";
    }
}

//Factory class, which is designed as a single example
public class BoxFactory {
    private HashMap<String,AbstractBox> map;
    //Initialize in the constructor
    private BoxFactory() {
        map = new HashMap<>();
        map.put("I",new IBox());
        map.put("L",new LBox());
        map.put("O",new OBox());
    }
    //Get drawing objects by name
    public AbstractBox getShape(String name){
        return map.get(name);
    }
    //Provide a method to get the factory class object
    private static BoxFactory boxFactory = new BoxFactory();
    public static BoxFactory getInstance(){
        return boxFactory;
    }
}

//test
public class Client {
    public static void main(String[] args) {
        //Get I drawing objects
        AbstractBox i = BoxFactory.getInstance().getShape("I");
        i.display("grey");
        //Get O drawing objects
        AbstractBox o = BoxFactory.getInstance().getShape("O");
        o.display("gules");
        //Get O drawing objects
        AbstractBox o1 = BoxFactory.getInstance().getShape("O");
        o1.display("black");

        System.out.println(o==o1);
    }
}

Advantages, disadvantages and usage scenarios

advantage

  • Greatly reduce the number of similar or identical objects in memory, save system resources and provide system performance
  • The external state in the sharing mode is relatively independent and does not affect the internal state

shortcoming
In order to make objects can be shared, it is necessary to externalize some states of shared meta objects, separate internal states from external states, and make program logic complex

Usage scenario

  • A system has a large number of the same or similar objects, resulting in a large amount of memory consumption
  • Most of the state of an object can be externalized, and these externalizations can be passed into the object
  • When using the sharing mode, you need to maintain a sharing pool for storing sharing objects, which requires certain system resources. Therefore, it is worth using the sharing mode when you need to reuse the sharing objects for many times

JDK source code analysis

The Integer class uses meta mode

Behavioral patterns (11)

Behavioral pattern is used to describe the complex process control of a program at run time, that is, how multiple classes or objects cooperate with each other to complete tasks that can not be completed by a single object. It involves the allocation of responsibilities between algorithms and objects

Behavioral patterns are divided into class behavior patterns and object behavior patterns. The former uses inheritance mechanism to allocate behavior between classes, and the latter uses combination or aggregation to allocate behavior between objects. Because the coupling degree of composite relationship or aggregation relationship is lower than that of inheritance relationship and meets the "composite Reuse Principle", the object behavior pattern has more flexibility than the class behavior pattern

Behavioral patterns are divided into:

  • Template method pattern
  • Strategy mode
  • Command mode
  • Responsibility chain model
  • State mode
  • Observer mode
  • Intermediary model
  • Iterator mode
  • Visitor mode
  • Memo mode
  • Interpreter mode

Template method pattern

Define the algorithm skeleton in an operation, and delay some steps of the algorithm to the subclass, so that the subclass can redefine some specific steps of the algorithm without changing the structure of the algorithm

structure

Abstract class( Abstract Class): Responsible for giving the outline and skeleton of an algorithm. It consists of a template method and several basic methods
	Template method: defines the skeleton of the algorithm and calls its basic methods in a certain order
	Basic method: it is the method to realize each step of the algorithm and an integral part of the template method. The basic methods can be divided into three types:
		Abstract method( Abstract Method): An abstract method is declared by an abstract class and implemented by its concrete subclasses
		Specific method( Concrete Method): A concrete method is declared and implemented by an abstract class or concrete class, and its subclasses can be overridden or inherited directly
		Hook method( Hook Method): It has been implemented in abstract classes, including logical methods for judgment and empty methods that need subclass rewriting
 Specific subclass( Concrete Class): Implement the abstract methods and hook methods defined in the abstract class, which are the constituent steps of a top-level logic

Case realization

Stir fry

//Template method, abstract class, defining template method and basic method
public abstract class AbstractClass {
    //Template method definition
    public final void cookProcess(){
        pourOil();
        heatOil();
        pourVegetable();
        pourSauce();
        fry();
    }
    public void pourOil(){
        System.out.println("Pour oil");
    }
    public void heatOil(){
        System.out.println("hot oil");
    }
    //Put in the ingredients
    public abstract void pourVegetable();
    //Put in the seasoning
    public abstract void pourSauce();
    public void fry(){
        System.out.println("Stir fry");
    }
}

//Subclass
public class ConcreteClass_BaoCai extends AbstractClass{
    @Override
    public void pourVegetable() {
        System.out.println("Put meat");
    }

    @Override
    public void pourSauce() {
        System.out.println("Put chili");
    }
}

//Subclass
public class ConcreteClass_CaiXin extends AbstractClass{
    @Override
    public void pourVegetable() {
        System.out.println("Put green vegetables");
    }

    @Override
    public void pourSauce() {
        System.out.println("Put salt");
    }
}

//test
public class Client {
    public static void main(String[] args) {
        ConcreteClass_BaoCai c1 = new ConcreteClass_BaoCai();
        c1.cookProcess();

        ConcreteClass_CaiXin c2 = new ConcreteClass_CaiXin();
        c2.cookProcess();
    }
}

Advantages and disadvantages

advantage

  • Improve code reusability, put the same part of the code in the abstract parent class, and put different code in different subclasses
  • It realizes the reverse control, calls the operation of its subclasses through a parent class, extends different behaviors through the specific implementation of subclasses, and realizes the reverse control, which conforms to the 'opening and closing principle'

shortcoming

  • A subclass needs to be defined for each different implementation, which leads to an increase in the number of classes, a larger system and a more abstract design
  • Abstract methods in the parent class are implemented by subclasses, and the execution results of subclasses will affect the results of the parent class, which leads to a reverse control structure, which improves the difficulty of code reading

Applicable scenario

  • The overall steps of the algorithm are very fixed, but when individual parts are changeable, you can use the template method pattern to abstract the easily changeable parts for subclass implementation
  • It is necessary to determine the execution time of a step in the parent algorithm through the subclass, so as to realize the reverse control of the subclass over the parent

JDK source code analysis

The InputStream class uses the template method pattern. The abstract read method is defined in the InputStream class and called

Strategy mode

This pattern defines a number of column algorithms and encapsulates each algorithm so that they can be replaced with each other, and the change of the algorithm will not affect the customers using the algorithm. Policy pattern tree object behavior pattern, which separates the responsibility of using the algorithm from the implementation of the algorithm by encapsulating the algorithm, and delegates it to different objects to manage these algorithms

structure

Abstract Strategy: This is an abstract role, usually implemented by an interface or abstract class. This role gives the interfaces required for all specific policy classes
Concrete Strategy: it implements the interface of abstract policy definition and provides specific algorithm implementation or behavior
Context: holds a reference to a policy class, which is finally called to the client

case

sales promotion

//Abstract policy class
public interface Strategy {
    public void show();
}

//Specific policy class, encapsulation algorithm
public class StrategyA implements Strategy{
    @Override
    public void show() {
        System.out.println("20 minus 100");
    }
}

//Specific policy class, encapsulation algorithm
public class StrategyB implements Strategy{
    @Override
    public void show() {
        System.out.println("10% off");
    }
}

//Specific policy class, encapsulation algorithm
public class StrategyC implements Strategy{
    @Override
    public void show() {
        System.out.println("50 vouchers for 200");
    }
}

//Environment, promoter, aggregation strategy
public class SalesMan {
    //Aggregation strategy
    private Strategy strategy;
    public SalesMan(Strategy strategy) {
        this.strategy = strategy;
    }
    //The content of the event is displayed by the promoter
    public void salesManShow(){
        strategy.show();
    }
}

//test
public class Client {
    public static void main(String[] args) {
        //Spring festival activities
        SalesMan salesMan1 = new SalesMan(new StrategyA());
        salesMan1.salesManShow();
        //Valentine's Day activities
        SalesMan salesMan2 = new SalesMan(new StrategyB());
        salesMan2.salesManShow();
        //Store celebration activities
        SalesMan salesMan3 = new SalesMan(new StrategyC());
        salesMan3.salesManShow();
    }
}

Advantages and disadvantages

advantage

  • Policy classes can be switched freely. Since policy classes all implement the same interface, they can be switched freely
  • It is easy to expand. To add a new policy class, you only need to add a specific policy class. There is basically no need to change the original code, which conforms to the "opening and closing principle"
  • Avoid using multiple conditional selection statements (if else) and fully reflect the idea of object-oriented design

shortcoming

  • The client must know all policy classes and decide which policy class to use
  • The policy pattern will result in many policy classes. You can reduce the number of objects to a certain extent by using the meta pattern

Usage scenario

  • When a system needs to dynamically select one of the centralized algorithms, each algorithm can be encapsulated into a policy class
  • A class defines a variety of behaviors, and these behaviors appear in the form of multiple conditional statements in the operation of this class. Conditional branches can be moved into their respective policy classes to replace these conditional statements
  • Each algorithm in the system is completely independent of each other, and it is required to hide the implementation details of the specific algorithm from the customer
  • When the system requires that the customer using the algorithm should not know the data it operates, the policy pattern can be used to hide the data structure related to the algorithm

JDK source code analysis

The policy pattern in Comparator has a sort method in the Arrays class

Command mode

Encapsulating a request as an object separates the responsibility of sending the request from the responsibility of executing the request. In this way, the two communicate through the command object, which is convenient to store, transfer, call, add and manage the command object

structure

Abstract Command class role (Command): defines the interface of the Command and declares the method to execute
Concrete Command: a specific command that implements the command interface; Usually, the receiver will be held and the receiver's function will be called to complete the operation to be performed by the command
Implementer, Receiver role: Receiver, the object that actually executes the command. Any class can become a Receiver as long as it can implement the corresponding functions required by the command
Caller and requester roles (Invoker): command objects are required to execute requests. They usually hold command objects, and many command objects can be held. This is similar to the place where the client actually triggers the command and requires the command to perform the corresponding operation, that is, it is equivalent to using the entry of the command object

Sample code

Ordering process

//Abstract command class
public interface Command {
    void execute();
}

//Specific command class
public class OrderCommand implements Command{
    //Holding recipient object
    private SeniorChef seniorChef;
    private Order order;
    public OrderCommand(SeniorChef seniorChef, Order order) {
        this.seniorChef = seniorChef;
        this.order = order;
    }
    @Override
    public void execute() {
        System.out.println(order.getDiningTable());
        order.getFoodDir().entrySet().forEach(x ->
                seniorChef.makeFood(x.getKey(),x.getValue())
        );
        System.out.println("finish");
    }
}

//Order class,
public class Order {
    //Table number
    private int diningTable;
    //Meals and scores
    private Map<String,Integer> foodDir = new HashMap<>();
    public int getDiningTable() {
        return diningTable;
    }
    public void setDiningTable(int diningTable) {
        this.diningTable = diningTable;
    }
    public Map<String, Integer> getFoodDir() {
        return foodDir;
    }
    public void setFoodDir(String name, int num) {
        foodDir.put(name,num);
    }
}

//Chef class
public class SeniorChef {
    public void makeFood(String name,int num){
        System.out.println(name +" "+num);
    }
}

//The waiter class belongs to the caller role
public class Waitor {
    //Hold multiple command objects
    private List<Command> commands = new ArrayList<>();
    public void setCommand(Command cmd){
        commands.add(cmd);
    }
    //Function of initiating command
    public void orderUp(){
        System.out.println("Here comes the order");
        for (Command command : commands) {
            if(command!=null){
                command.execute();
            }
        }
    }
}

//test
public class Client {
    public static void main(String[] args) {
        //Create order object
        Order order1 = new Order();
        order1.setDiningTable(1);
        order1.setFoodDir("Potato silk",1);
        order1.setFoodDir("rice",1);

        Order order2 = new Order();
        order2.setDiningTable(1);
        order2.setFoodDir("Dried tofu in old soup",1);
        order2.setFoodDir("rice",1);

        //Create chef object
        SeniorChef seniorChef = new SeniorChef();
        //Create command object
        OrderCommand cmd1 = new OrderCommand(seniorChef,order1);
        OrderCommand cmd2 = new OrderCommand(seniorChef,order2);
        //Create attendant
        Waitor waitor = new Waitor();
        waitor.setCommand(cmd1);
        waitor.setCommand(cmd2);
        //The waiter initiates an order
        waitor.orderUp();
    }
}

Advantages and disadvantages

advantage

  • Reduce the coupling of the system. Command mode can decouple the object that invokes the operation from the object that implements the operation
  • Adding or deleting commands is very convenient. Using the command mode, adding and deleting commands will not affect other classes. It meets the opening and closing principle and is flexible for expansion
  • Macro commands can be implemented. Command mode can be combined with combined mode to assemble multiple commands into a combined command, that is, macro command
  • It is convenient to realize Undo and Redo operations. The command mode can be combined with the memo mode described later to realize the revocation and recovery of commands

shortcoming

  • Using command mode may cause some systems to have too many specific command classes
  • The system structure is more complex

Usage scenario

  • The system needs to decouple the request caller and the request receiver, so that the caller and the receiver do not interact directly
  • System requirements specify requests, queue requests, and execute requests at different times
  • The system needs to support Undo and Redo operations of commands

JDK source code analysis

Runnable is a typical command mode. Runnable plays the role of command, Thread acts as the caller, and start method is the execution method

Responsibility chain model

Also known as the responsibility chain mode, in order to avoid coupling the request sender with multiple request handlers, the standing of all requests are connected into a chain by the migration object remembering the reference of the next object. When a request occurs, the request can be passed along the chain until an object handles it

structure

Abstract Handler: defines an interface for processing requests, including abstract processing methods and a subsequent connection
Concrete Handler: implement the processing method of the abstract handler to judge whether the request can be processed. If the request can be processed, it will be processed. If not, the request will be transferred to its successor
client: create a processing chain and submit a request to the specific handler object of the chain head. It doesn't care about the processing details and the transmission process of the request

Sample code

leave

//written request for leave
public class LeaveRequest {
    private String name;
    private int num;
    private String context;
    public LeaveRequest(String name, int num, String context) {
        this.name = name;
        this.num = num;
        this.context = context;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getNum() {
        return num;
    }
    public void setNum(int num) {
        this.num = num;
    }
    public String getContext() {
        return context;
    }
    public void setContext(String context) {
        this.context = context;
    }
}

//group leader
public class GroupLeader extends Handler {
    public GroupLeader() {
        super(0,Handler.num_one);
    }
    @Override
    protected void handlerLeave(LeaveRequest leave) {
        System.out.println(leave.getName()+" leave"+leave.getNum()+"Day reason:"+leave.getContext());
        System.out.println("The team leader agrees");
    }
}

//division manager
public class Manager extends Handler {
    public Manager() {
        super(Handler.num_one,Handler.num_three);
    }
    @Override
    protected void handlerLeave(LeaveRequest leave) {
        System.out.println(leave.getName()+" leave"+leave.getNum()+"Day reason:"+leave.getContext());
        System.out.println("The Department Manager agrees");
    }
}

//general manager
public class GeneralManager extends Handler {
    public GeneralManager() {
        super(Handler.num_three,Handler.num_seven);
    }
    @Override
    protected void handlerLeave(LeaveRequest leave) {
        System.out.println(leave.getName()+" leave"+leave.getNum()+"Day reason:"+leave.getContext());
        System.out.println("The general manager agrees");
    }
}

//Abstract handler class
public abstract class Handler {
    protected final static int num_one = 1;
    protected final static int num_three = 3;
    protected final static int num_seven = 7;
    //Leave days handled by the leader
    private int numStart;
    private int numEnd;
    //Declare successor (superior leader)
    private Handler nextHandler;
    public void setNextHandler(Handler nextHandler) {
        this.nextHandler = nextHandler;
    }
    public Handler(int numStart) {
        this.numStart=numStart;
    }
    public Handler(int numStart, int numEnd) {
        this.numStart = numStart;
        this.numEnd = numEnd;
    }
    //Methods for leaders at all levels to deal with leave requests
    protected abstract void handlerLeave(LeaveRequest leave);
    //Submit leave slip
    public final void submit(LeaveRequest leave){
        //Approved by the leader
        this.handlerLeave(leave);
        if(this.nextHandler != null && leave.getNum()>this.numEnd){
            //Submit to superior
            this.nextHandler.submit(leave);
        }else{
            System.out.println("End of process");
        }
    }
}

//test
public class Client {
    public static void main(String[] args) {
        //Create a leave slip object
        LeaveRequest leaveRequest = new LeaveRequest("Xiao Ming", 1, "ill");
        //Create leaders at all levels
        GroupLeader groupLeader = new GroupLeader();
        Manager manager = new Manager();
        GeneralManager generalManager = new GeneralManager();
        //Set processing chain
        groupLeader.setNextHandler(manager);
        manager.setNextHandler(generalManager);
        //Xiao Ming submits an application for leave
        groupLeader.submit(leaveRequest);
    }
}

Advantages and disadvantages

advantage

  • This mode reduces the coupling between the sender and receiver of the request
  • The scalability of the system is enhanced. New request processing classes can be added as needed to meet the opening and closing principle
  • It increases the flexibility of assigning responsibilities to objects. When the workflow changes, you can dynamically change the members in the chain or modify their order, or dynamically add or delete responsibilities
  • The responsibility chain simplifies the connection between objects. An object only needs to maintain a reference to its successor without maintaining the references of all other processors, avoiding the use of many if or if... else statement
  • Responsibility sharing: each class only needs to deal with its own work, and those that cannot be handled are transferred to the next object for completion. The scope of responsibility of each class is clarified, which conforms to the principle of single responsibility of the class

shortcoming

  • There is no guarantee that every request will be processed. Since a request does not have a clear receiver, it cannot be guaranteed that it will be processed. The request may reach the end of the chain and cannot be processed
  • For a long responsibility chain, the processing of requests may involve multiple processing objects, which will have a certain impact on the system performance
  • The rationality of establishing the responsibility chain depends on the client, which increases the complexity of the client and may lead to system errors due to the error of the responsibility chain, such as circular call

Source code analysis

In Java Web, FilterChain is a typical application of the responsibility chain (filter) pattern

State mode

For stateful objects, complex 'judgment logic' is extracted into different state objects, allowing state objects to change their behavior when their internal state changes

structure

Context: also known as context, it defines the interface required by the client program, maintains a current state, and delegates state related operations to the current state object for processing
Abstract state role: defines an interface to encapsulate the behavior corresponding to a specific state in an environment object
Context State: implements the behavior corresponding to the abstract state

Code example

//Abstract state class
public abstract class LiftState {
    //Declare environment role class objects
    public Context context;
    public void setContext(Context context) {
        this.context = context;
    }
    public abstract void open();
    public abstract void close();
    public abstract void run();
    public abstract void stop();
}

//Environment role class
public class Context {
    //Defines constants for the corresponding state object
    public final static OpenningState opening_state = new OpenningState();
    public final static OpenningState closing_state = new OpenningState();
    public final static OpenningState running_state = new OpenningState();
    public final static OpenningState stopping_state = new OpenningState();
    //Defines a current elevator state variable
    private LiftState liftState;
    public LiftState getLiftState() {
        return liftState;
    }
    public void setLiftState(LiftState liftState) {
        this.liftState = liftState;
        this.liftState.setContext(this);
    }
    public void open(){
        this.liftState.open();
    }
    public void close(){
        this.liftState.close();
    }
    public void run(){
        this.liftState.run();
    }
    public void stop(){
        this.liftState.stop();
    }
}

//Elevator opening status class
public class OpenningState extends LiftState{
    @Override
    public void open() {
        System.out.println("open");
    }

    @Override
    public void close() {
        //Set status
        super.context.setLiftState(Context.closing_state);
        //Call the close method in the context in the current state
        super.context.close();
    }

    @Override
    public void run() {
        //Don't do anything?
    }

    @Override
    public void stop() {
        //Don't do anything?
    }
}

//test
public class Client {
    public static void main(String[] args) {
        Context context = new Context();
        context.setLiftState(new RunningState());

        context.open();
        context.close();
        context.run();
        context.stop();
    }
}

Advantages and disadvantages

advantage

  • Put all the behaviors related to a state into one class, and you can easily add new states. You can change the behavior of the object by changing the state of the object
  • Allow state transition logic and what is the issue of state objects, rather than a huge conditional statement block

shortcoming

  • The use of state mode will inevitably increase the number of system classes and objects
  • The structure and implementation of state pattern are complex. If it is not used properly, it will lead to the confusion of program structure and code
  • The state mode does not support the opening and closing principle very well

Usage scenario

  • When an object's behavior depends on its state and it must change its behavior according to its state at run time, you can consider using state mode
  • An operation contains a huge branch structure, and these branches depend on the state of the object

Observer mode

Also known as publish subscribe mode, it defines a one to many dependency, allowing multiple observer objects to listen to a topic object at the same time. When the state of the subject object changes, it will notify all observer objects that they can update themselves automatically

structure

Subject: abstract topic (Abstract observer). The abstract topic role saves all observer objects in a collection. Each topic can be used by any number of observers. The abstract topic provides an interface to add and delete observer objects
ConcreteSubject: a specific subject (specific observer). This role stores the relevant status in the specific observer object. When the internal status of a specific subject changes, it sends a notice to all registered observers
Observer: an abstract observer, which defines an update interface to update itself when notified of a topic change
ConcreteObserver: a concrete observer, which implements the update interface defined by the abstract observer, so as to update its own status when notified of the subject change

Code example

WeChat official account

//Abstract observer
public interface Observer {
    void update(String message);
}

//Specific observer
public class WeiXinUser implements Observer{
    private String name;
    public WeiXinUser(String name) {
        this.name = name;
    }
    @Override
    public void update(String message) {
        System.out.println(name+"-"+message);
    }
}

//Abstract theme
public interface Subject {
    //Add subscriber (add observer)
    void attach(Observer observer);
    //Delete subscriber
    void detach(Observer observer);
    //Notify subscribers of updates
    void notify(String message);
}

//Specific theme
public class SubscriptionSubject implements Subject{
    //Define a collection to hold multiple observers
    private List<Observer> weixinUserList = new ArrayList<>();
    @Override
    public void attach(Observer observer) {
        weixinUserList.add(observer);
    }
    @Override
    public void detach(Observer observer) {
        weixinUserList.remove(observer);
    }
    @Override
    public void notify(String message) {
        //Traversal set
        for (Observer observer : weixinUserList) {
            observer.update(message);
        }
    }
}

public class Client {
    public static void main(String[] args) {
        //Create official account number
        SubscriptionSubject subject = new SubscriptionSubject();
        //Subscribing to official account
        subject.attach(new WeiXinUser("Arilia"));
        subject.attach(new WeiXinUser("an introduction to"));
        subject.attach(new WeiXinUser("Cherish"));
        subject.attach(new WeiXinUser("EZ"));
        //The official account is updated.
        subject.notify("Go to the gym tomorrow");
    }
}

Advantages and disadvantages

advantage

  • It reduces the coupling relationship between the target and the observer, which is an abstract coupling relationship
  • The observed sends a notification, and all registered observers will receive the information

shortcoming

  • If there are many observers, it will take time for all observers to receive the notification sent by the observed
  • If the observer has circular dependency, the notification sent by the observer will make the observer call circularly, which will lead to system crash

Usage scenario

  • There is a one to many relationship between objects. Changes in the state of one object will affect other objects
  • When an abstract model has two aspects, one of which depends on the other

Implementation provided in JDK

In Java, observer patterns are defined through the Observer class (topic) and observer interface (observer). As long as their subclasses are implemented, observer pattern instances can be written

Intermediary model

It is also called mediation mode. It defines an intermediary role to encapsulate the interaction between a series of objects, so that the coupling between the original objects is loose, and the interaction between them can be changed independently

structure

Abstract Mediator role: it is the interface of the Mediator and provides the abstract method for the colleague object to register and forward the colleague object information
Concrete mediator: implement the mediator interface, define a List to manage colleague objects and coordinate the interaction between various colleague roles. Therefore, it depends on colleague roles
Abstract Colleague role: define the interface of Colleague class, save the mediator object, provide the abstract method of Colleague object interaction, and realize the public functions of all Colleague classes that sell to each other
Concrete Colleague role: it is the implementer of the abstract colleague class. When it is necessary to interact with other colleague objects, the mediator object is responsible for the subsequent interaction

Sample code

rent an apartment

//Abstract mediator class
public abstract class Mediator {
    //Communication methods
    public abstract void constact(String message,Person person);
}

//Specific intermediary
public class MediatorStructure extends Mediator{
    //Aggregate homeowners and renters
    private HouseOwner houseOwner;
    private Tenant tenant;
    public HouseOwner getHouseOwner() {
        return houseOwner;
    }
    public void setHouseOwner(HouseOwner houseOwner) {
        this.houseOwner = houseOwner;
    }
    public Tenant getTenant() {
        return tenant;
    }
    public void setTenant(Tenant tenant) {
        this.tenant = tenant;
    }
    @Override
    public void constact(String message, Person person) {
        if (person == houseOwner){
            tenant.getMessage(message);
        }else{
            houseOwner.getMessage(message);
        }
    }
}

//Abstract colleague class
public abstract class Person {
    protected String name;
    protected Mediator mediator;
    public Person(String name, Mediator mediator) {
        this.name = name;
        this.mediator = mediator;
    }
}

//Specific colleague role classes
public class Tenant extends Person{
    public Tenant(String name, Mediator mediator) {
        super(name, mediator);
    }
    //Methods of communicating with intermediaries
    public void constact(String message){
        mediator.constact(message,this);
    }
    //Methods of obtaining information
    public void getMessage(String message){
        System.out.println("rent an apartment "+name+" information"+message);
    }
}

//Specific colleague role classes
public class HouseOwner extends Person{
    public HouseOwner(String name, Mediator mediator) {
        super(name, mediator);
    }
    //Methods of communicating with intermediaries
    public void constact(String message){
        mediator.constact(message,this);
    }
    //Methods of obtaining information
    public void getMessage(String message){
        System.out.println("Homeowner "+name+" information"+message);
    }
}

//test
public class Client {
    public static void main(String[] args) {
        //Create mediator object
        MediatorStructure mediator = new MediatorStructure();
        //Create tenant object
        Tenant tenant = new Tenant("lisi", mediator);
        //Create homeowner object
        HouseOwner houseOwner = new HouseOwner("zhangsan", mediator);
        //Intermediaries should know the specific homeowners and renters
        mediator.setTenant(tenant);
        mediator.setHouseOwner(houseOwner);

        tenant.constact("I want to rent a house");
        houseOwner.constact("I have a house here");
    }
}

Advantages and disadvantages

advantage
loosely coupled
The mediator model encapsulates the interaction between multiple colleague objects into the mediator object, so that the colleague objects are loosely coupled and can basically achieve complementary dependence. In this way, colleague objects can be changed and reused independently, instead of affecting the whole body as before

Centralized control interaction
The interaction of multiple colleague objects is encapsulated in the mediator object for centralized management, so that when these interaction behaviors change, only the mediator object needs to be modified. Of course, if the system is ready, the mediator object needs to be extended, and each peer class does not need to be modified

One to many association is transformed into one to one association
When the mediator mode is not used, the relationship between colleague objects is usually one to many. After the mediator object is introduced, the relationship between mediator object and colleague object usually becomes two-way one-to-one, which will make the relationship between objects easier to understand and implement

shortcoming
When there are too many colleagues, the intermediary's responsibilities will be great, and he will become complex and huge, so that the system is difficult to maintain

Usage scenario

  • There is a load reference relationship between objects in the system, and the system structure is chaotic and difficult to understand
  • When you create an object that runs between multiple classes and do not want to generate new subclasses

Iterator mode

Provides an object to sequentially access a series of data in an aggregate object without exposing the internal representation of the aggregate object

structure

Abstract aggregate role: defines the interface for storing, adding and deleting aggregate elements and creating iterator objects at the first level
Concrete aggregate: implements an abstract aggregate class and returns an instance of a concrete iterator
Abstract Iterator role: defines the interface for accessing and traversing aggregation elements, usually including hasNext, next and other methods
Concrete iterator role: implement the method defined in the abstract iterator interface, complete the traversal of the aggregate object, and record the current location of the traversal

Code example

Define a container object that can store student objects, and hand over the function of traversing the container to the iterator

//Abstract iterator role
public interface StudentIterator {
    //There are elements when judging
    boolean hasNext();
    //Get next element
    Student next();
}

//Concrete Iterator 
public class StudentIteratorImpl implements StudentIterator{
    private List<Student> list;
    private int index=0;
    public StudentIteratorImpl(List<Student> list) {
        this.list = list;
    }
    @Override
    public boolean hasNext() {
        return index<list.size();
    }

    @Override
    public Student next() {
        Student student = list.get(index);
        index++;
        return student;
    }
}

//Abstract aggregate role
public interface StudentAggregate {
    //Add student function
    void addStudent(Student student);
    //Delete student function
    void removeStudent(Student student);
    //Get iterator object function
    StudentIterator getStudentIterator();
}

//Specific aggregate roles
public class StudentAggregateImpl implements StudentAggregate{
    private List<Student> list = new ArrayList<>();
    @Override
    public void addStudent(Student student) {
        list.add(student);
    }

    @Override
    public void removeStudent(Student student) {
        list.remove(student);
    }

    @Override
    public StudentIterator getStudentIterator() {
        return new StudentIteratorImpl(list);
    }
}

//element
public class Student {
    private String name;
    private String number;
    public Student() {
    }
    public Student(String name, String number) {
        this.name = name;
        this.number = number;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getNumber() {
        return number;
    }
    public void setNumber(String number) {
        this.number = number;
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", number='" + number + '\'' +
                '}';
    }
}

public class Client {
    public static void main(String[] args) {
        //Create aggregate object
        StudentAggregateImpl studentAggregate = new StudentAggregateImpl();
        //Add element
        studentAggregate.addStudent(new Student("zhangsan","111"));
        studentAggregate.addStudent(new Student("lisi","222"));
        studentAggregate.addStudent(new Student("wangwu","333"));
        studentAggregate.addStudent(new Student("zhangliu","444"));
        //Traversing aggregate objects

        //Get iterator object
        StudentIterator studentIterator = studentAggregate.getStudentIterator();
        while (studentIterator.hasNext()){
            Student next = studentIterator.next();
            System.out.println(next.toString());
        }
    }
}

Advantages and disadvantages

advantage

  • It supports traversing an aggregate object in different ways, and multiple traversal methods can be defined on the same aggregate object. In the iterator mode, we only need to replace the original iterator with a different iterator to change the traversal algorithm. We can also define the subclass of the iterator to support the new traversal method
  • Iterators simplify aggregate classes. Due to the introduction of iterator, there is no need to provide data traversal and other methods in the original aggregation object, which can simplify the design of aggregation class
  • In the iterator mode, it is convenient to add new aggregation classes and iterator classes due to the introduction of the abstraction layer, without modifying the original code, and meet the requirements of the opening and closing principle

shortcoming
The number of classes is increased, which increases the complexity of the system to a certain extent

Usage scenario

  • When you need to provide multiple traversal methods for aggregate objects
  • When it is necessary to provide a unified interface for traversing different aggregation structures
  • When accessing the contents of an aggregate object without exposing the representation of its internal details

Visitor mode

Encapsulates some operations that act on the elements of a data structure. It can define the operations that act on these elements without changing the data structure

structure

Abstract Visitor role:
It defines the behavior of accessing each Element. Its parameters are the accessible elements. Theoretically, the number of methods is the same as the number of elements. It is not difficult to see that the visitor pattern requires that the number of Element classes cannot be changed
Concrete visitor role:
Give the specific behavior generated when accessing each element class
Abstract Element role:
Defines a method to receive visitors, which means that every element can be accessed by visitors
Concrete element role:
Provide a specific implementation of the receiving access method, which usually uses the method provided by the visitor to access the element class
Object Structure role:
The object structure mentioned in the definition is an abstract expression. Specifically, it can be understood as a class with container nature or object characteristics. It will contain a group of elements and can iterate these elements for visitors to visit

Code example

Feeding pets
Visitor role: the person who feeds the pet
Specific visitor roles: host, others
Abstract element character: Animal
Specific element roles: cat, dog
Structure object role: master home

//Abstract element role class
public interface Animal {
    //Receive visitor access
    void accept(Person person);
}

//Concrete element class
public class Dog implements Animal{
    @Override
    public void accept(Person person) {
        person.feed(this);
        System.out.println("Woof, woof");
    }
}

//Concrete element role class
public class Cat implements Animal{
    @Override
    public void accept(Person person) {
        person.feed(this);
        System.out.println("cat ");
    }
}

//Visitor role class
public interface Person {
    //feed
    void feed(Cat cat);
    void feed(Dog dog);
}

//Specific visitors
public class Owner implements Person{
    @Override
    public void feed(Cat cat) {
        System.out.println("The owner feeds the cat");
    }

    @Override
    public void feed(Dog dog) {
        System.out.println("The owner feeds the dog");
    }
}

//Specific visitors
public class Someone implements Person{
    @Override
    public void feed(Cat cat) {
        System.out.println("Friends feed cats");
    }

    @Override
    public void feed(Dog dog) {
        System.out.println("Friends feed dogs");
    }
}

//Structure object
public class Home {
    //Declare a collection object to store element objects
    private List<Animal> list = new ArrayList<>();
    //Add element function
    public void add(Animal animal){
        list.add(animal);
    }
    //
    public void action(Person person){
        for (Animal animal:list){
            animal.accept(person);
        }
    }
}

public class Client {
    public static void main(String[] args) {
        //Create home object
        Home home = new Home();
        //Add element
        home.add(new Cat());
        home.add(new Dog());
        //Create master object
        Owner owner = new Owner();
        home.action(owner);
    }
}

Advantages and disadvantages

advantage

  • It has good expansibility and adds new functions to the elements in the object structure without modifying the elements in the object structure
  • Good reusability. The visitor defines the general functions of the whole object, so as to improve the degree of reusability
  • Separate irrelevant behaviors, separate irrelevant behaviors through visitors, and encapsulate relevant behaviors to form a visitor, so that each visitor has a single function

shortcoming

  • It is very difficult to change the object structure. In the visitor mode, without adding a new element class, the corresponding specific operation should be added to each specific visitor. This is the principle of opening and closing
  • In violation of the dependency lead principle, the visitor pattern relies on concrete classes instead of abstract classes

Usage scenario

  • The object structure is relatively stable, but its total algorithm often changes
  • The objects in the object structure need to provide a variety of different and irrelevant operations, and the changes of these operations should not affect the structure of the object

Memo mode

Also called snapshot mode, it captures the internal state of an object without destroying the encapsulation, and saves the state outside the object, so that the object can be restored to the original saved state when necessary in the future

structure

Initiator role: it records the internal status information at the current time, provides the function of creating memo data, and implements a total of other businesses. It can access all the information in the memo
Memo role: it is responsible for storing the internal status of the initiator and providing these internal status to the initiator when necessary
Caretaker: it manages memos and provides the functions of protecting and obtaining memos, but it cannot access and modify the contents of memos

Narrow interface: the narrow interface of the memo when the manager object sees it. This narrow interface only allows him to pass the memo object to other objects
Wide interface: Contrary to the narrow interface seen by the management sister, the initiator object can see a wide interface that allows it to read all data and recover the internal state of the initiator object according to the data

Code example

Game play boss
White box memo mode (wide interface)

//Memo role class
public class RoleStateMemento {
    private int vit;
    private int atk;
    private int def;
    public RoleStateMemento() {
    }
    public RoleStateMemento(int vit, int atk, int def) {
        this.vit = vit;
        this.atk = atk;
        this.def = def;
    }
    public int getVit() {
        return vit;
    }
    public void setVit(int vit) {
        this.vit = vit;
    }
    public int getAtk() {
        return atk;
    }
    public void setAtk(int atk) {
        this.atk = atk;
    }
    public int getDef() {
        return def;
    }
    public void setDef(int def) {
        this.def = def;
    }
}

//Game role class (initiator role)
public class GameRole {
    private int vit;//Life value
    private int atk;//aggressivity
    private int def;//Defensive power
    //Initialize internal state
    public void initState(){
        this.vit=100;
        this.atk=100;
        this.def=100;
    }
    //battle
    public void fight(){
        this.vit=0;
        this.atk=0;
        this.def=0;
    }
    //Save role status function
    public RoleStateMemento saveState(){
        return new RoleStateMemento(vit,atk,def);
    }
    //Restore role initialization state
    public void recoverState(RoleStateMemento roleStateMemento){
        this.vit=roleStateMemento.getVit();
        this.atk=roleStateMemento.getAtk();
        this.def=roleStateMemento.getDef();
    }
    //Display status
    public void stateDisplay(){
        System.out.println("GameRole{" +
                "vit=" + vit +
                ", atk=" + atk +
                ", def=" + def +
                '}');
    }
    public int getVit() {
        return vit;
    }
    public void setVit(int vit) {
        this.vit = vit;
    }
    public int getAtk() {
        return atk;
    }
    public void setAtk(int atk) {
        this.atk = atk;
    }
    public int getDef() {
        return def;
    }
    public void setDef(int def) {
        this.def = def;
    }
}

//Manager role
public class RoleStateCaretaker {
    //Declare a variable of type rolestatemento
    private RoleStateMemento roleStateMemento;
    public RoleStateMemento getRoleStateMemento() {
        return roleStateMemento;
    }
    public void setRoleStateMemento(RoleStateMemento roleStateMemento) {
        this.roleStateMemento = roleStateMemento;
    }
}

public class Client {
    public static void main(String[] args) {
        System.out.println("----War boss front----");
        GameRole gameRole = new GameRole();
        gameRole.initState();
        gameRole.stateDisplay();
        //backups
        RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker();
        roleStateCaretaker.setRoleStateMemento(gameRole.saveState());

        System.out.println("----War boss after----");
        gameRole.fight();
        gameRole.stateDisplay();
        System.out.println("----recovery----");
        gameRole.recoverState(roleStateCaretaker.getRoleStateMemento());
        gameRole.stateDisplay();
    }
}

In the black box memo mode (narrow interface), the way to realize the dual interface is to design the memo class into an internal member class that initiates human beings

//Memorandum interface, providing external narrow interface
public interface Memento {
}

//Game role class (initiator role)
public class GameRole {
    private int vit;//Life value
    private int atk;//aggressivity
    private int def;//Defensive power
    //Initialize internal state
    public void initState(){
        this.vit=100;
        this.atk=100;
        this.def=100;
    }
    //battle
    public void fight(){
        this.vit=0;
        this.atk=0;
        this.def=0;
    }
    //Save role status function
    public Memento saveState(){
        return new RoleStateMemento(vit,atk,def);
    }
    //Restore role initialization state
    public void recoverState(Memento memento){
        RoleStateMemento roleStateMemento = (RoleStateMemento)memento;
        this.vit=roleStateMemento.getVit();
        this.atk=roleStateMemento.getAtk();
        this.def=roleStateMemento.getDef();
    }
    //Display status
    public void stateDisplay(){
        System.out.println("GameRole{" +
                "vit=" + vit +
                ", atk=" + atk +
                ", def=" + def +
                '}');
    }
    public int getVit() {
        return vit;
    }
    public void setVit(int vit) {
        this.vit = vit;
    }
    public int getAtk() {
        return atk;
    }
    public void setAtk(int atk) {
        this.atk = atk;
    }
    public int getDef() {
        return def;
    }
    public void setDef(int def) {
        this.def = def;
    }

    private class RoleStateMemento implements Memento{
        private int vit;//Life value
        private int atk;//aggressivity
        private int def;//Defensive power
        public RoleStateMemento() {
        }
        public RoleStateMemento(int vit, int atk, int def) {
            this.vit = vit;
            this.atk = atk;
            this.def = def;
        }
        public int getVit() {
            return vit;
        }
        public void setVit(int vit) {
            this.vit = vit;
        }
        public int getAtk() {
            return atk;
        }
        public void setAtk(int atk) {
            this.atk = atk;
        }
        public int getDef() {
            return def;
        }
        public void setDef(int def) {
            this.def = def;
        }
    }
}

//Manager role
public class RoleStateCaretaker {
    //Declare a variable of type rolestatemento
    private Memento roleStateMemento;
    public Memento getRoleStateMemento() {
        return roleStateMemento;
    }
    public void setRoleStateMemento(Memento roleStateMemento) {
        this.roleStateMemento = roleStateMemento;
    }

}

public class Client {
    public static void main(String[] args) {
        System.out.println("----War boss front----");
        GameRole gameRole = new GameRole();
        gameRole.initState();
        gameRole.stateDisplay();
        //backups
        RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker();
        roleStateCaretaker.setRoleStateMemento(gameRole.saveState());

        System.out.println("----War boss after----");
        gameRole.fight();
        gameRole.stateDisplay();
        System.out.println("----recovery----");
        gameRole.recoverState(roleStateCaretaker.getRoleStateMemento());
        gameRole.stateDisplay();
    }
}

Advantages and disadvantages

advantage

  • Provides a mechanism to restore state. When users need it, they can easily restore the data to a historical state
  • The encapsulation of internal state is realized. Except for the initiator who created it, no other object can access this status information
  • Simplified the human. The initiator does not need to manage and save each backup of its internal status. All status information is saved in the memo and managed by the manager, which is in line with the principle of single responsibility

shortcoming
Large resource consumption, if you want to save the internal

Usage scenario

  • Scenes that need to save and restore data, such as the archiving function of intermediate results when playing games
  • You need to provide a scenario for rollback operations, such as word, notepad and other software using Ctrl+Z, as well as transaction operations in the database

Interpreter mode

Define your own grammar

structure

Abstract expression role:
Define the interface of the interpreter and agree on the interpretation operation of the interpreter, mainly including the interpretation method interpret
Terminal expression role: it is a subclass of abstract expression, which is used to implement operations related to terminals in grammar. Each terminal in grammar has a specific mediator expression corresponding to it
Nonterminal expression role: it is also a subclass of abstract expression, which is used to implement operations related to non terminators in grammar. Each rule in grammar corresponds to a non intermediary expression
Context: it usually contains the data or common functions required by each interpreter. It is generally used to transfer the data shared by all interpreters. Subsequent interpreters can obtain these values from here
Client (Client): the main task is to translate the sentences or expressions that need analysis into the abstract syntax tree described by the interpreter object, then invoke the interpreter's interpretation method, and, of course, can access the interpreter's interpretation method through the introduction of the environment role.

Code example

Calculate a - (b+c)

//Abstract expression class
public abstract class AbstractExpression {
    //Method of interpretation
    public abstract int interpret(Context context);
}

//Environment role class
public class Context {
    //Define a map set to store variables and corresponding values
    private Map<Variable,Integer> map = new HashMap<>();
    //Function of adding variables
    public void assign(Variable variable,Integer integer){
        map.put(variable,integer);
    }
    //Obtain the corresponding value according to the variable
    public int getValue(Variable variable){
        return map.get(variable);
    }

}

//Addition expression class
public class Plus extends AbstractExpression{
    //+Expression around sign
    private AbstractExpression left;
    private AbstractExpression right;
    public Plus(AbstractExpression left, AbstractExpression right) {
        this.left = left;
        this.right = right;
    }
    @Override
    public String toString() {
        return "Plus{" +
                "left=" + left +
                ", right=" + right +
                '}';
    }
    @Override
    public int interpret(Context context) {
        //Add the results of the left and right expressions
        return left.interpret(context)+right.interpret(context);
    }
}

//Classes that encapsulate variables
public class Variable extends AbstractExpression{
    //Declare a member variable that stores the variable name
    private String name;
    public Variable(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "Variable{" +
                "name='" + name + '\'' +
                '}';
    }
    @Override
    public int interpret(Context context) {
        return context.getValue(this);
    }
}

public class Minus extends AbstractExpression{
    //-Expression around sign
    private AbstractExpression left;
    private AbstractExpression right;
    public Minus(AbstractExpression left, AbstractExpression right) {
        this.left = left;
        this.right = right;
    }
    @Override
    public String toString() {
        return "Plus{" +
                "left=" + left +
                ", right=" + right +
                '}';
    }
    @Override
    public int interpret(Context context) {
        //Add the results of the left and right expressions
        return left.interpret(context)-right.interpret(context);
    }
}

public class Client {
    public static void main(String[] args) {
        //Create environment objects
        Context context = new Context();
        //Create multiple variables
        Variable a = new Variable("a");
        Variable b = new Variable("b");
        Variable c = new Variable("c");
        //Store variables in environment objects
        context.assign(a,1);
        context.assign(b,2);
        context.assign(c,3);
        //Get abstract syntax tree a+b-c
        AbstractExpression expression = new Minus(a,new Plus(b,c));
        int interpret = expression.interpret(context);
        System.out.println(interpret);
    }
}

Advantages and disadvantages

advantage

  • Easy to change and expand grammar. Since classes are used in interpreter mode to represent the grammar rules of the language, the grammar can be changed or extended by inheriting hierarchy. Each grammar rule can be expressed as a class, so it is convenient to implement a simple language
  • It is easier to implement grammar. In the abstract syntax tree, the implementation of each expression node class is similar, and the coding of these classes will not be particularly complex
  • It is more convenient to add a new interpretation expression. If you need to add a new interpretation expression, you only need to add a new terminator expression or non terminator expression class accordingly. The original expression class code does not need to be modified and conforms to the opening and closing principle

shortcoming

  • It is difficult to maintain complex grammar. In the interpreter mode, at least one class needs to be defined for each rule. Therefore, if a language contains too many grammar rules, the number of classes will increase sharply, making it difficult for the system to manage and maintain
  • Low execution efficiency. Because a large number of loops and recursive calls are used in the interpreter mode, the speed of interpreting more complex sentences is very slow, and the modulation process of the code is also troublesome

Usage scenario

  • When the grammar of the language is relatively simple and the execution efficiency is not the key problem
  • When the problem occurs repeatedly and can be expressed in a simple language
  • When a language needs interpretation and execution, and the sentences in the language can be expressed as an abstract syntax tree

Comprehensive exercise

Analysis of Spring IOC related interfaces

BeanFactory parsing

As the top-level interface, BeanFactory defines the basic functional specifications of IOC container
Beanfactory has three important sub interfaces:
ListableBeanFactory
HierarchicalBeanFactory
AutowireCapableBeanFactory
The final default implementation class is DefaultListableBeanFactory

Why define so many interfaces
Each interface has its application occasion, which is mainly to distinguish the transfer and transformation of objects and the restrictions on object data access during the internal operation of spring, such as

  • The ListableBeanFactory interface indicates that these beans can be listed (stored)
  • Hierarchical beanfactory indicates that these beans are inherited, that is, each Bean may have a parent Bean
  • The AutowireCapableBeanFactory interface defines the automatic assembly rules of beans

BeanFactory has a very important sub interface, the ApplicationContext interface, which is mainly used to regulate that the bean objects in the container are loaded without delay, that is, the bean is initialized and stored in the container when the container object is created

Implementation of IOC container provided by Spring:

  • ClasspathXmlApplicationContext: loads the xml configuration file according to the classpath and creates an IOC container
  • FileSystemXmlApplicationContext: load the xml configuration file according to the system path and create the IOC container
  • AnnotationConfigApplicationContext: loads the annotation class configuration and creates an IOC container

BeanDefinition parsing

Bean objects are described by BeanDefinition in the Spring implementation

BeanDefinitionReader parsing

BeanDefinitionReader is used to parse Spring configuration files to obtain Bean objects

BeanDefinitionRegistry parsing

BeanDefinitionReader is used to parse bean definitions and encapsulate them into BeanDefinition objects. Many bean tags are defined in the configuration file we define. These beans are parsed and sent to the BeanDefinition registry, and the top-level interface of the registry is BeanDefinitionRegistry

Customize spring IOC

<beans>
	<bean id="userService" class="com.xx.UserServiceImpl">
		<property name="userDao" ref="userDao"></property>
	</bean>
	<bean id="userDao" class="com.xx.userDao"></bean>
</beans>

Define bean related POJOs

Define the PropertyValue class, corresponding to the property tag object in the bean tag

/**
 * Used to encapsulate the properties of the property tag under the bean tag
 * name attribute
 * ref attribute
 * value attribute
 */
public class PropertyValue {
    private String name;
    private String ref;
    private String value;
    public PropertyValue() {
    }
    public PropertyValue(String name, String ref, String value) {
        this.name = name;
        this.ref = ref;
        this.value = value;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getRef() {
        return ref;
    }
    public void setRef(String ref) {
        this.ref = ref;
    }
    public String getValue() {
        return value;
    }
    public void setValue(String value) {
        this.value = value;
    }
}

A bean tag can have multiple property sub tags, so define a MutablePropertyValues class to store and manage multiple PropertyValue objects

/**
 * Used to store and manage multiple PropertyValue objects
 */
public class MutablePropertyValues implements Iterable<PropertyValue> {
    //Define a list object to store the PropertyValue object
    private final List<PropertyValue> propertyValues;
    public MutablePropertyValues(){
        propertyValues = new ArrayList<>();
    }
    public MutablePropertyValues(List<PropertyValue> propertyValues) {
        if (propertyValues == null) {
            this.propertyValues = new ArrayList<>();
        } else {
            this.propertyValues = propertyValues;
        }
    }
    //Get all PropertyValue objects and return in the form of array
    public PropertyValue[] getPropertyValues(){
        return propertyValues.toArray(new PropertyValue[0]);
    }
    //Gets the PropertyValue object based on the name property value
    public PropertyValue getPropertyValue(String propertyName){
        for (PropertyValue propertyValue : propertyValues){
            if(propertyValue.getName().equals(propertyName)){
                return propertyValue;
            }
        }
        return null;
    }
    //Determine whether the collection is empty
    public boolean isEmpty(){
        return propertyValues.isEmpty();
    }
    //Add PropertyValue object
    public MutablePropertyValues addPropertyValue(PropertyValue propertyValue){
        //Determine whether the propertyValue object stored in the collection duplicates the one passed in
        for (int i = 0; i < propertyValues.size(); i++) {
            PropertyValue propertyValue1 = propertyValues.get(i);
            if(propertyValue.getName().equals(propertyValue1.getName())){
                propertyValues.set(i,propertyValue);
                return this;
            }
        }
        propertyValues.add(propertyValue);
        return this;
    }
    //Judge whether there is an object with the specified name attribute value
    public boolean contains(String propertyName){
        return getPropertyValue(propertyName)!=null;
    }
    //Get iterator object
    @Override
    public Iterator<PropertyValue> iterator() {
        return propertyValues.iterator();
    }
}

BeanDefinition class is used to encapsulate bean information, mainly including id, class, property and other attributes and tags

/**
 * Used to encapsulate bean tag data
 * id attribute
 * class attribute
 * property Sub label
 */
public class BeanDefinition {
    private String id;
    private String className;
    private MutablePropertyValues mutablePropertyValues;
    public BeanDefinition(){
        mutablePropertyValues = new MutablePropertyValues();
    }
    public BeanDefinition(String id, String className, MutablePropertyValues mutablePropertyValues) {
        this.id = id;
        this.className = className;
        this.mutablePropertyValues = mutablePropertyValues;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }

    public MutablePropertyValues getMutablePropertyValues() {
        return mutablePropertyValues;
    }

    public void setMutablePropertyValues(MutablePropertyValues mutablePropertyValues) {
        this.mutablePropertyValues = mutablePropertyValues;
    }
}

Define registry related classes

Create the BeanDefinitionRegistry interface and define the relevant operations of the registry, as follows:

/**
 * Registry object class, registry related operations
 */
public interface BeanDefinitionRegistry {
    //Register the BeanDefinition object into the registry
    void registerBeanDefinition(String beanName,BeanDefinition beanDefinition);

    //Deletes the BeanDefinition object with the specified name from the registry
    void removeBeanDefinition(String beanName) throws Exception;

    //Gets the BeanDefinition object from the registry by name
    BeanDefinition getBeanDefinition(String beanName) throws Exception;

    //Determines whether the registry contains a BeanDefinition object with the specified name
    boolean containsBeanDefinition(String beanName);

    //Gets the number of BeanDefinition objects in the registry
    int getBeanDefinitionCount();

    //Gets the names of all beandefinitions in the registry
    String[] getBeanDefinitionName();
}

The SimpleBeanDefinitionRegistry class implements the BeanDefinitionRegistry interface and defines the Map collection as the registry container

public class SimpleBeanDefinitionRegistry implements BeanDefinitionRegistry{
    private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();

    @Override
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {
        beanDefinitionMap.put(beanName,beanDefinition);
    }

    @Override
    public void removeBeanDefinition(String beanName) throws Exception {
        beanDefinitionMap.remove(beanName);
    }

    @Override
    public BeanDefinition getBeanDefinition(String beanName) throws Exception {
        return beanDefinitionMap.get(beanName);
    }

    @Override
    public boolean containsBeanDefinition(String beanName) {
        return beanDefinitionMap.containsKey(beanName);
    }

    @Override
    public int getBeanDefinitionCount() {
        return beanDefinitionMap.size();
    }

    @Override
    public String[] getBeanDefinitionName() {
        return beanDefinitionMap.keySet().toArray(new String[0]);
    }
}

Define parser related classes

The BeanDefinitionReader interface is used to parse the configuration file and register the bean information in the registry

public interface BeanDefinitionReader {
    //Get registry object
    BeanDefinitionRegistry getRegistry();
    //Load the configuration file and register in the registry
    void loadBeanDefinitions(String configLocation) throws Exception;
}

XmlBeanDefinitionReader class is specially used to parse xml configuration files. This class implements BeanDefinitionReader interface and two functions in the interface

/**
 * Parsing the xml configuration file
 */
public class XmlBeanDefinitionReader implements BeanDefinitionReader{
    //Declare registry objects
    private BeanDefinitionRegistry registry;

    public XmlBeanDefinitionReader() {
        this.registry = new SimpleBeanDefinitionRegistry();
    }

    @Override
    public BeanDefinitionRegistry getRegistry() {
        return registry;
    }

    @Override
    public void loadBeanDefinitions(String configLocation) throws Exception {
        //Parsing xml configuration files using dom4j
        SAXReader reader = new SAXReader();
        //Get the configuration file under the classpath
        InputStream is = XmlBeanDefinitionReader.class.getClassLoader().getResourceAsStream(configLocation);
        Document document = reader.read(is);
        //Gets the root label object from the Document object
        Element rootElements = document.getRootElement();
        //Get all bean tags under the root tag
        List<Element> beanElements = rootElements.element("bean");
        //Traversal set
        for(Element element : beanElements){
            String id = element.attributeValue("id");
            String className = element.attributeValue("class");
            BeanDefinition beanDefinition = new BeanDefinition();
            beanDefinition.setId(id);
            beanDefinition.setClassName(className);

            //establish
            MutablePropertyValues mutablePropertyValues = new MutablePropertyValues();

            //Get all property objects under the bean tag
            List<Element> propertyElements = rootElements.element("property");
            for(Element pElement : propertyElements){
                String name = pElement.attributeValue("name");
                String ref = pElement.attributeValue("ref");
                String value = pElement.attributeValue("value");
                PropertyValue propertyValue = new PropertyValue(name, ref, value);
                mutablePropertyValues.addPropertyValue(propertyValue);
            }
            //Encapsulate MutablePropertyValues object into beanDefinition object
            beanDefinition.setMutablePropertyValues(mutablePropertyValues);
            //Register beanDefinition in the registry
            registry.registerBeanDefinition(id,beanDefinition);
        }
    }
}

IOC container related classes

BeanFactory interface
Define the unified specification of IOC container in this interface, that is, get bean object

public interface BeanFactory {
    //Get the bean object according to the name of the bean object
    Object getBean(String name) throws Exception;
    //Obtain the bean object according to the name of the bean object and perform type conversion
    <T> T getBean(String name,Class<? extends T>clazz) throws  Exception;
}

ApplicationContext interface
All sub implementation classes of the interface create bean objects without delay, so the refresh method is defined in the interface

/**
 * Define non delayed loading function
 */
public interface ApplicationContext extends BeanFactory{
    void refresh() throws Exception;
}

AbstractApplicationContext class

  • As a subclass of the ApplicationContext interface, this class is also non delayed loading, so you need to define a map collection in this class as a container for bean object storage
  • Declare variables of BeanDefinitionReader type, which are used to parse xml configuration files, and comply with the principle of single responsibility. The creation of objects of BeanDefinitionReader type is implemented by subclasses, because only subclasses specify which subclass of BeanDefinitionReader is created to implement the object
/**
 * ApplicationContext Sub implementation class of interface
 * For immediate loading
 */
public abstract class AbstractApplicationContext implements ApplicationContext{
    protected BeanDefinitionReader beanDefinitionReader;
    //Container for storing bean objects
    protected Map<String,Object> singletonObjects = new HashMap<>();
    //Variables that declare the configuration file path
    protected String configLocation;
    @Override
    public void refresh() throws Exception {
        //Load BeanDefinition object
        beanDefinitionReader.loadBeanDefinitions(configLocation);
        //Initialize bean
        finishBeanInitialization();
    }

    //bean initialization
    private void finishBeanInitialization() throws Exception{
        //Get registry object
        BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry();
        //Get beanDefinition object
        String[] beanNames = registry.getBeanDefinitionName();
        for (String beanName : beanNames){
            //Initialize the bean
            getBean(beanName);
        }
    }
}

ClassPathXmlApplicationContext class
This class is mainly used to load the configuration file under the class path and create bean objects. It mainly completes the following functions:

  • In the constructor, create the BeanDefinitionReader object
  • In the construction method, the refresh method is invoked to load the configuration file, create the bean object and store it in the container.
  • Rewrite the getBean method in the parent interface and implement the dependency injection operation
/**
 * IOC Container specific subclasses
 * It is used to load the configuration file in xml format under the classpath
 */
public class ClassPathXmlApplicationContext extends AbstractApplicationContext {

    public ClassPathXmlApplicationContext(String configLocation){
        this.configLocation=configLocation;
        //Building parser objects
        beanDefinitionReader = new XmlBeanDefinitionReader();
        try{
            refresh();
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    //Get the bean object according to the name of the bean object
    public Object getBean(String name) throws Exception{
        //Determines whether the bean object with the specified name is contained in the object container
        Object o = singletonObjects.get(name);
        if(o != null){
            return o;
        }
        //Get beanDefinition object
        BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry();
        BeanDefinition beanDefinition = registry.getBeanDefinition(name);
        //Get class in bean
        String className = beanDefinition.getClassName();
        //Create objects with reflections
        Class<?> aClass = Class.forName(className);
        Object o1 = aClass.newInstance();
        //Dependency injection
        MutablePropertyValues mutablePropertyValues = beanDefinition.getMutablePropertyValues();
        for (PropertyValue propertyValue : mutablePropertyValues){
            String pName = propertyValue.getName();
            String pValue = propertyValue.getValue();
            String pRef = propertyValue.getRef();
            if(pRef!=null && "".equals(pRef)){
                //Get the dependent bean object
                Object bean = getBean(pRef);
                //Splicing method name
                String methodName = StringUtils.getSetterMethodName(pName);
                //Get all method objects
                Method[] methods = aClass.getMethods();
                for (Method method:methods){
                    if(methodName.equals(method.getName())){
                        method.invoke(o1,bean);
                    }
                }
            }
            if(pValue!=null && "".equals(pValue)){
                //Splicing method name
                String methodName = StringUtils.getSetterMethodName(pName);
                Method method = aClass.getMethod(methodName,String.class);
                method.invoke(o1,pValue);
            }
        }

        //Add the container before returning the object
        singletonObjects.put(name,o1);
        return o1;
    }

    public <T> T getBean(String name,Class<? extends T> clazz) throws Exception{
        Object bean = getBean(name);
        if(bean == null){
            return null;
        }
        return clazz.cast(bean);
    }
}

summary

Topics: Java Design Pattern