Learning of common design patterns

Posted by ex247 on Wed, 22 Dec 2021 22:11:19 +0100

1. Design pattern

1.1 overview:

Software Design Pattern Software design pattern, also known as design pattern, is a set of code design experience that is repeatedly used, known by most people, classified and catalogued. It describes some recurring problems in the process of software design and the solutions to the problems. In other words, it is a series of routines to solve specific problems and the code design of predecessors The summary of experience has certain universality and can be used repeatedly.

1.2 necessity of learning design patterns

The essence of design pattern is the practical application of object-oriented design principles. It is a full understanding of class encapsulation, inheritance and polymorphism, as well as class association and composition.

Using design patterns correctly has the following advantages.

  • It can improve the thinking ability, programming ability and design ability of programmers.
  • Make the program design more standardized and the code preparation more engineering, greatly improve the efficiency of software development, and shorten the software development cycle.
  • The designed code has high reusability, strong readability, high reliability, good flexibility and strong maintainability.

1.3 design pattern classification

  • Create pattern

    It is used to describe "how to create objects". Its main feature is "separating the creation and use of objects". GoF (group of four) provides five creation modes: singleton, prototype, factory method, abstract factory and builder.

  • Structural model

    It is used to describe how to form a larger structure of classes or objects according to a certain layout. GoF (group of four) provides seven 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 (group of four) provides 11 behavioral models, such as template method, strategy, command, responsibility chain, state, observer, mediator, iterator, visitor, memo and interpreter.

1.4 OOP seven principles

  • Opening and closing principle: open to extension and close to modification
  • Richter substitution principle: inheritance must ensure that the properties owned by the superclass are still valid in the subclass
  • Dependency Inversion Principle: interface oriented programming, not implementation oriented programming.
  • Single responsibility principle: control the granularity of classes, decouple objects and improve their cohesion.
  • Interface isolation principle: establish special interfaces for each class
  • Dimitri's Law: talk only to your direct friends, not to "strangers".
  • Composition Reuse Principle: try to use association relations such as composition or aggregation first, and then consider using inheritance relations.

2. Create pattern

The main focus of creative mode is how to create objects?, Its main feature is "separating the creation and use of objects".

This can reduce the coupling degree of the system, and users do not need to pay attention to the creation details of objects.

The creation mode is divided into:

  • Singleton mode
  • Factory method model
  • Abstract engineering pattern
  • Prototype mode
  • Builder pattern

2.1 singleton mode

Singleton Pattern is one of the simplest design patterns in Java. This type of design pattern belongs to creation pattern, which provides the best way to create objects.

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.

2.1. 1. Structure of singleton mode

The main roles of singleton mode are as follows:

  • Singleton class. Only one instance of a class can be created
  • Access class. Using singleton classes

2.1. 2 implementation of singleton mode

There are two types of singleton design patterns:

Hungry Chinese style: class loading will cause the single instance object to be created

Lazy: class loading does not cause the single instance object to be created, but only when the object is used for the first time

1. Hungry Han style - mode 1 (static variable mode)

/**
 * Hungry Han style
 *      Static variables create objects of classes
 */
public class Singleton {
    //Private construction method
    private Singleton() {}

    //Create an object of this class at the member location
    private static Singleton instance = new Singleton();

    //Provide a static method to get the object
    public static Singleton getInstance() {
        return instance;
    }
}

explain:

This method declares a static variable of Singleton type at the member position and creates an instance object of Singleton class. The instance object is created as the class loads. If the object is large enough and has not been used, it will cause a waste of memory.

2. Hungry Chinese - mode 2 (static code block mode)

/**
 * Evil Han style
 *      Create this type of object in a static code block
 */
public class Singleton {

    //Private construction method
    private Singleton() {}

    //Create an object of this class at the member location
    private static Singleton instance;

    static {
        instance = new Singleton();
    }

    //Provide a static method to get the object
    public static Singleton getInstance() {
        return instance;
    }
}

explain:

In this way, a static variable of Singleton type is declared at the member position, and the object is created in the static code block and for the loading of the class. Therefore, it is basically the same as hungry man mode 1. Of course, this mode also has the problem of memory waste.

3.DCL Lehan style

The double check lock mode after adding volatile keyword is a better singleton implementation mode, which can ensure thread safety and no performance problems in the case of multithreading.

/**
 * DCL Lehan type (double detection lock + volatile)
 */
public class Singleton {
    //Construction method private
    private Singleton(){}

    //Create an object of this class at the member location
    private static volatile Singleton instance;

    //Provide external methods to get the object
    public static Singleton getInstance(){
        //In the first judgment, if instance is not null, it will not enter the lock grabbing stage and directly return to the actual state
        if (instance == null){
            synchronized (Singleton.class){
                //After grabbing the lock, judge whether it is empty again
                if (instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

4. Static internal class

In the static internal class singleton mode, instances are created by internal classes. Because the JVM will not load static internal classes during the process of loading external classes, only the properties / methods of internal classes will be loaded and their static properties will be initialized. Because static attributes are modified by static, they are guaranteed to be instantiated only once, and the instantiation order is strictly guaranteed.

/**
 * Static inner class mode
 */
public class Singleton {
    //Private construction method
    private Singleton(){}

    private static class SingletonHolder{
        private static final Singleton INSTANCE = new Singleton();
    }

    //Provide a static method to get the object
    public static Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }
}

explain:

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.

Summary:

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

5. Enumeration method

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 this feature 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.

/**
 * Enumeration mode
 */
public enum Singleton {
    INSTANCE;
}

explain:

The enumeration method belongs to the evil Chinese method.

2.1. 3 existing problems

2.1. 3.1 problem demonstration

Destroy singleton mode:

Enables the Singleton class defined above to create multiple objects, except enumeration. There are two methods, serialization and reflection.

Serialization and deserialization

Singleton class:

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

    private static class SingletonHolder{
        private static final Singleton INSTANCE = new Singleton();
    }

    //Provide a static method to get the object
    public static Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }
}

Test class:

public class Test {
    public static void main(String[] args) {
        //writeObjectToFile();
        Singleton read = (Singleton)read();
        Singleton  read2 = (Singleton)read();
        System.out.println(read);
        System.out.println(read2);

    }

    public static Object read(){
        Singleton singleton = null;
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream("D:\\WeChat\\a.txt"));
            singleton = (Singleton)ois.readObject();
            return singleton;
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }finally {
            try {
                ois.close();
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                return singleton;
            }

        }
    }

    public static void writeObjectToFile(){
        Singleton instance = Singleton.getInstance();
        ObjectOutputStream oos = null;
        try {
         oos = new ObjectOutputStream(new FileOutputStream("D:\\WeChat\\a.txt"));
            oos.writeObject(instance);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                oos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

The running result of the above code is false, indicating that serialization and deserialization have broken the singleton design pattern.

reflex

Singleton class:

public class Singleton {

    //Private construction method
    private Singleton() {}
    
    private static volatile Singleton instance;

    //Provide a static method to get the object
    public static Singleton getInstance() {

        if(instance != null) {
            return instance;
        }

        synchronized (Singleton.class) {
            if(instance != null) {
                return instance;
            }
            instance = new Singleton();
            return instance;
        }
    }
}

Test class:

public class Test {
    public static void main(String[] args) throws Exception {
        //Gets the bytecode object of the Singleton class
        Class clazz = Singleton.class;
        //Gets the private parameterless constructor object of the Singleton class
        Constructor constructor = clazz.getDeclaredConstructor();
        //Cancel access check
        constructor.setAccessible(true);

        //Create object s1 of Singleton class
        Singleton s1 = (Singleton) constructor.newInstance();
        //Create an object of the Singleton class s2
        Singleton s2 = (Singleton) constructor.newInstance();

        //Determine whether two Singleton objects created by reflection are the same object
        System.out.println(s1 == s2);
    }
}

Note: the enumeration method will not cause these two problems.

2.1. 3.2 problem solving

  • Solution of destroying singleton pattern by serialization and deserialization

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.

Singleton class:

public class Singleton implements Serializable {

    //Private construction method
    private Singleton() {}

    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    //Provide a static method to get the object
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
    
    /**
     * The following is to solve the serialization and deserialization cracking singleton mode
     */
    private Object readResolve() {
        return SingletonHolder.INSTANCE;
    }
}

  • Solution of cracking single case in reflection mode
public class Singleton {

    //Private construction method
    private Singleton() {
        /*
           Code to be added for reflection cracking singleton mode
        */
        if(instance != null) {
            throw new RuntimeException();
        }
    }
    
    private static volatile Singleton instance;

    //Provide a static method to get the object
    public static Singleton getInstance() {

        if(instance != null) {
            return instance;
        }

        synchronized (Singleton.class) {
            if(instance != null) {
                return instance;
            }
            instance = new Singleton();
            return instance;
        }
    }
}

The Runtime class is the singleton design pattern used.

2.2 factory mode

summary

effect:

  • It realizes the separation of creator and caller
    • Instantiated objects do not use new, but use factory methods instead
    • The implementation class will be selected to create objects for unified management and control. This decouples the caller from our implementation class.
  • Detailed classification
    • Simple factory mode
      • It is used to produce any product in the same hierarchical structure (for adding new products, the existing code needs to be overwritten)
    • Factory method model
      • It is used to produce fixed products in the same hierarchical structure (any product can be added)
    • Abstract factory pattern
      • Create other factories around one super factory. The super factory is also known as the factory of other factories.
  • Seven principles of OOP:
    • Opening and closing principle
    • Dependence Inversion Principle
    • Dimitt's law

2.2. 1 simple factory mode

Simple factory is not a design pattern, but more like a programming habit.

structure

The simple factory contains the following roles:

  • 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.

realization

Factory class codes are as follows:

public class CarFactory {
    public  Car getCar(String car){
        if (car.equals("Wuling")){
            return new WuLing();
        } else if (car.equals("Tesla")) {
            return new Tesla();
        }else {
            return null;
        }
    }
}

The factory handles the details of creating objects. Once there is a CarFactory, getCar() in the CarStore class becomes the customer of this object. Later, if you need a Car object, you can get it directly from the factory. In this way, the coupling with the Car implementation class is released, and new coupling is generated, including the coupling between CarStore object and CarFactory factory object, and the coupling between factory object and commodity object.

If a new type of car is added in the later stage, we need to modify the CarFactory code, which violates the opening and closing principle. There may be many factory class clients, such as creating meituan takeout. In this way, you only need to modify the factory class code and save other modification operations.

Advantages and disadvantages

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 original 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".

extend

Static factory

In the development, some people define the function of creating objects in the factory class as static. This is the static factory pattern, which is not one of the 23 design patterns. The code is as follows:

public class CarFactory {
    public static Car getCar(String car){
        if (car.equals("Wuling")){
            return new WuLing();
        } else if (car.equals("Tesla")) {
            return new Tesla();
        }else {
            return null;
        }
    }
}

2.2. 2 factory method mode

In view of the shortcomings in the above example, the factory method mode can be used to solve them perfectly and fully follow 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

The main roles of the factory approach model are:

  • 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 implements the abstract methods in the abstract factory and completes the creation of specific products.
  • Abstract Product: it defines the specification of the Product and describes the main characteristics and functions of the Product.
  • Concrete product: it implements the interface defined by the abstract product role, which is created by the specific factory and corresponds to the specific factory one by one.

Abstract factory:

public interface CarFactory {
    Car getCar();
}

Specific factory:

public class TeslaFactory implements CarFactory{
    @Override
    public Car getCar() {
        return new Tesla();
    }
}

public class WulLingFactory implements CarFactory{
    @Override
    public Car getCar() {
        return new WuLing();
    }
}

Consumer category

public class Consumer {
    public static void main(String[] args) {
        Car car = new WulLingFactory().getCar();
        Car car2 = new TeslaFactory().getCar();
        car.name();
        car2.name();
    }
}

From the code written above, we can see that when adding product classes, we should also add factory classes accordingly. There is no need to modify the code of factory classes, which solves the shortcomings of simple factory mode.

The factory method pattern is a further abstraction of the simple factory pattern. Due to the use of polymorphism, the factory method pattern not only maintains the advantages of the simple factory pattern, but also overcomes its disadvantages.

Advantages and disadvantages

advantage:

  • Users only need to know the name of the specific factory to get the desired product, without knowing the specific creation process of the product;
  • When adding new products 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.

2.2. 3 Abstract Factory Pattern

concept

It is a pattern structure that provides an interface for the access class to create a group of related or interdependent objects, and the access class can obtain different levels of products of the same family without specifying the specific class of the product.

Abstract factory pattern is an upgraded version of factory method pattern. Factory method pattern only produces one level of products, while abstract factory pattern can produce multiple levels of products.

structure

The main roles of the abstract factory pattern are as follows:

  • 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 features 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 and is created by the concrete factory. It has a many-to-one relationship with the concrete factory.

realization

Abstract factory:

public interface MyFactory {
    Phone getPhone();
    Computer getComputer();
}

Specific factory:

public class HuaWeiFactory implements MyFactory {
    @Override
    public Phone getPhone() {
        return new HuaWeiPhone();
    }

    @Override
    public Computer getComputer() {
        return new HuaWeiComputer();
    }
}


public class MiUiFactory implements MyFactory {
    @Override
    public Phone getPhone() {
        return new MiUiPhone();
    }

    @Override
    public Computer getComputer() {
        return new MiUiComputer();
    }
}

If you want to add the same product family, you only need to add another corresponding factory class without modifying other classes.

Advantages and disadvantages

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.

Disadvantages:

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

Usage scenario

  • When the objects to be created are a series of interrelated or interdependent product families, such as televisions, washing machines, air conditioners, etc.

  • There are multiple product families in the system, but only one of them is used at a time. If someone only likes to wear clothes and shoes of a certain brand.

  • The class library of products is provided in the system, and the interfaces of all products are the same. The client does not depend on the creation details and internal structure of product instances.

For example, the input method changes the skin, and the whole set changes together. Generate programs for different operating systems.

2.2. 4 mode extension

Simple factory + profile decoupling

You can decouple factory objects and product objects by means of 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

tesla=com.wdzl.factory.config_factory.Tesla
wuling=com.wdzl.factory.config_factory.WuLing

Step 2: improve the factory class

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

    //2. Load the configuration file only once
    static {
        //2.1 create Properties object
        Properties properties = new Properties();
        //2.2 loading the configuration file
        InputStream is = CarFactory.class.getClassLoader().getResourceAsStream("bean.properties");
        try {
            properties.load(is);
            //Get the full class name from the collection and create the object
             Set<Object> keys = properties.keySet();
            for (Object key : keys) {
                String className = properties.getProperty((String) key);
                 Class clazz = Class.forName(className);
                Car car = (Car) clazz.newInstance();
                map.put((String)key,car);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static Car createCar(String name){
        return map.get(name);
    }
}

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.

2.2. 5use in Java

Collection.iterator method

The Collection interface is an abstract factory class, and ArrayList is a concrete factory class; The Iterator interface is an abstract commodity class, and the Iter inner class in the ArrayList class is a concrete commodity class. In the concrete factory class, the iterator() method creates the object of the concrete commodity class.

In addition:

​ 1. The getInstance() method in the dateforamt class uses the factory pattern;

​ 2. The getInstance() method in the calendar class uses the factory pattern;

2.3 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

The prototype pattern contains the following roles:

  • Abstract prototype class: Specifies the clone() method that the concrete prototype object must implement.
  • Concrete prototype class: implement the clone() method of 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.

realization

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 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 is provided in the Object class in Java to implement shallow cloning. Cloneable interface is the Abstract prototype class in the above class diagram, and the sub implementation class that implements Cloneable interface is the specific prototype class. The code is as follows:

public class Realizetype implements Cloneable{

    public Realizetype(){
        System.out.println("The concrete prototype object has been created");
    }


    @Override
    protected Realizetype clone() throws CloneNotSupportedException {
        System.out.println("Specific prototype copied successfully!");
        return (Realizetype) super.clone();
    }
}

PrototypeTest (test access class):

public class Test {
    public static void main(String[] args) {
        Realizetype realizetype = new Realizetype();
        try {
            Realizetype clone = realizetype.clone();
            System.out.println("Is the specific object and clone the same object?" + (realizetype == clone));
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}

case

Using prototype mode to generate "three good students" award

Specific prototype classes:

public class Citation implements Cloneable{
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void show(){
        System.out.println(this.name+"Classmate: excellent performance in this year and rated as three good students");
    }

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

Test class:

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Citation citation = new Citation();
        citation.setName("Zhang San");

        Citation clone = citation.clone();
        clone.setName("Li Si");

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

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)

In the above case of "three good students" award, modify the name attribute of Citation class to Student type attribute. The code is as follows:

//Specific circular class
public class Citation implements Cloneable , Serializable {
    private Student student;

    public Student getStudent() {
        return student;
    }

    public void setStudent(Student student) {
        this.student = student;
    }

    public void show(){
        System.out.println(this.student.getName()+"Classmate: excellent performance in this year and rated as three good students");
    }

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

explain

The application of shallow clone object points to the memory address of the object pointed to by the original attribute. Copy the reference to the attribute of the reference type in the concrete prototype class (Citation). This situation requires deep cloning, and deep cloning requires object flow.

//Test class
public class Test {
    public static void main(String[] args) throws Exception {
        Citation citation = new Citation();
        Student s = new Student();
        s.setName("Zhang San");
        citation.setStudent(s);

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\WeChat\\a.txt"));
        oos.writeObject(citation);
        oos.close();

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\WeChat\\a.txt"));
        Citation clone = (Citation) ois.readObject();
        clone.getStudent().setName("Li Si");

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

Note: the Citation class and Student class must implement the Serializable interface, otherwise NotSerializableException will be thrown.

2.4 builder mode

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; With the same builder, 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

The Builder pattern contains the following roles:

  • Abstract Builder class: this interface specifies the creation of those parts of complex objects, and does not involve the creation of specific component objects.

  • ConcreteBuilder: it implements the Builder interface and completes the specific creation method of each component of a complex product. After the construction process is completed, it provides an example of the product.

  • Product: complex object to create.

  • Director: call the specific builder to create each part of the complex object. The director does not involve the information of the specific product, but is only responsible for ensuring that each part of the object is created completely or in a certain order.

example

Create shared bike

The production of bicycle is a complex process, which includes the production of frame, seat and other components. The frame is made of carbon fiber, aluminum alloy and other materials, and the seat is made of rubber, leather and other materials. For the production of bicycles, the builder model can be used.

Here Bike is a product, including frame, seat and other components; Builder is an abstract builder, MobikeBuilder and OfoBuilder are concrete builders; Director is the commander.

Product category:

//Bicycles
public class Bike {
    private String frame;
    private String 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;
    }

}

Abstract builder class:

// Abstract builder class
public abstract class Builder {
    protected Bike bike = new Bike();

    public abstract void buildFrame();
    public abstract void buildSeat();
    public abstract Bike createBike();

}

Specific Builders:

//Hello, bike Builder class
public class HelloBuilder extends Builder {
    @Override
    public void buildFrame() {
        bike.setFrame("Aluminum alloy frame");
    }

    @Override
    public void buildSeat() {
        bike.setSeat("Mink seat");
    }

    @Override
    public Bike createBike() {
        return bike;
    }
}

//Moby bike Builder class
public class MobikeBuilder extends Builder{
    @Override
    public void buildFrame() {
        bike.setFrame("Iron frame");
    }

    @Override
    public void buildSeat() {
        bike.setSeat("Leather seat");
    }

    @Override
    public Bike createBike() {
        return bike;
    }
}

Commander category:

//Conductor class
public class Director {
    private Builder builder;

    public Director(Builder builder) {
        this.builder = builder;
    }
    public Bike construct(){
        builder.buildFrame();
        builder.buildSeat();
        return builder.bike;
    }
}

be careful:

The above example is the general usage of the Builder mode. The commander class Director plays a very important role in the Builder mode. It is used to guide the specific Builder how to build the product, control the call order, and return the complete product class to the caller. However, in some cases, the system structure needs to be simplified, and the commander class can be combined with the abstract Builder

// Abstract builder class
public abstract class Builder {

    protected Bike mBike = new Bike();

    public abstract void buildFrame();
    public abstract void buildSeat();
    public abstract Bike createBike();
    
    public Bike construct() {
        this.buildFrame();
        this.BuildSeat();
        return this.createBike();
    }
}

explain:

This does simplify the system structure, but it also aggravates the responsibilities of the abstract builder class, and does not conform to the principle of single responsibility. If the construct() is too complex, it is recommended to package it into the Director.

Advantages and disadvantages

advantage:

  • The builder model is well encapsulated. 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 class 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, and 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 product creation process. The creation steps of complex products 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 there are new requirements, it can be completed by implementing a new builder class. Basically, there is no need to modify the previously tested code, so there will be no risk to the original functions. Comply with the opening and closing principle.

Disadvantages:

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.

Mode extension

In addition to the above purposes, the builder mode is also commonly used in development. 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 mode can be used for reconstruction.

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

    private Phone(Builder builder){
        this.cpu = builder.B_cpu;
        this.mainboard= builder.B_mainboard;
        this.memory = builder.B_memory;
        this.screen = builder.B_memory;
    }

    public static final class Builder{
        private String B_cpu;
        private String B_screen;
        private String B_memory;
        private String B_mainboard;

        public Builder() {
        }

        public Builder setB_cpu(String b_cpu) {
            B_cpu = b_cpu;
            return this;
        }

        public Builder setB_screen(String b_screen) {
            B_screen = b_screen;
            return this;
        }

        public Builder setB_memory(String b_memory) {
            B_memory = b_memory;
            return this;
        }

        public Builder setB_mainboard(String b_mainboard) {
            B_mainboard = b_mainboard;
            return this;
        }

        public Phone build(){
            return new Phone(this);
        }
    }

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

public class Test {
    public static void main(String[] args) {
        Phone phone = new Phone.Builder()
                .setB_cpu("intel")
                .setB_mainboard("ASUS")
                .setB_memory("Kingston")
                .setB_screen("Samsung")
                .build();
        System.out.println(phone);
    }
}

The refactored code is more convenient to use and can improve the development efficiency to some extent. In terms of software design, the requirements for programmers are relatively high.

2.5 comparison of creation mode

2.5. 1 factory method mode VS builder mode

The factory method pattern focuses on the creation of the overall object; The builder pattern focuses on the process of component construction, which is intended to create a complex object through precise construction step by step.

Let's give a simple example to illustrate the difference between the two. If we want to create a superman, if we use the factory method mode, we will directly produce a Superman with infinite power, flying and wearing underwear; If you use the builder mode, you need to assemble the hands, head, feet, trunk and other parts, and then wear your underwear outside, so a superman was born.

2.5. 2 abstract factory mode VS builder mode

Abstract factory mode realizes the creation of product families. 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. Its 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.

3. Structural model

Structural patterns describe how classes or objects can be arranged into larger structures. It is divided into class structured pattern and object structured pattern. The former uses inheritance mechanism to organize interfaces and classes, and the latter uses composition or aggregation to combine 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 structured pattern has more flexibility than the class structured pattern.

The structural mode is divided into the following 7 types:

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

3.1 agent mode

3.1. 1 Overview

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.

Agents in Java can be divided into static agents and dynamic agents according to the generation time of agent classes. Static proxy classes are generated at compile time, while dynamic proxy classes are generated dynamically at Java runtime. Dynamic agents include JDK agent and CGLib agent.

3.1. 2 Structure

The Proxy mode is divided into three roles:

  • Abstract Subject class: business methods implemented by declaring real subjects and proxy objects through interfaces or abstract classes.
  • Real Subject class: it implements the specific business in the abstract subject. It is the real object represented by the proxy object and 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.

3.1. 3 static agent

Let's experience the static agent through a case.

Train station ticket selling

If you want to buy a train ticket, you need to go to the railway station to buy a ticket, take a bus to the railway station, queue up and a series of operations, which is obviously more troublesome. The railway station has consignment points in many places. It's much more convenient for us to buy tickets at the consignment point. This example is actually a typical agency model. The railway station is the target object and the consignment point is the agent object.

The code is as follows:

//Abstract Subject class
public interface SellTickets {
    void sell();
}

//Real Subject class
public class TrainStation implements SellTickets {
    @Override
    public void sell() {
        System.out.println("Buy tickets at the railway station");
    }
}

//Proxy class
public class ProxyPoint implements SellTickets {

    //Reference train class
    private TrainStation trainStation = new TrainStation();

    @Override
    public void sell() {
        System.out.println("Receiving point service fee");
        trainStation.sell();
    }
}


public class Test {
    public static void main(String[] args) {
        ProxyPoint point = new ProxyPoint();
        point.sell();
    }
}

From the above code, we can see that the test class directly accesses the ProxyPoint class object, that is, ProxyPoint acts as the intermediary between the access object and the target object. The sell method is also enhanced (the proxy point charges some service fees).

3.1.4 JDK dynamic agent

We use dynamic agent to implement the above case. First, we use the dynamic agent provided by JDK. 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 proxy object to obtain proxy object.

//Ticket selling interface
public interface SellTickets {
    void sell();
}

//Ticket selling interface
public class TrainStation implements SellTickets {
    @Override
    public void sell() {
        System.out.println("Buy tickets at the railway station");
    }
}

//Proxy factory, used to create proxy objects
public class ProxyFactory {
    //Declare target object
    private TrainStation trainStation = new TrainStation();

    //Get proxy object
    public SellTickets getProxyPoint(){
        //Get Proxy object using Proxy
        /**
         * newProxyInstance()Method parameter description:
         * ClassLoader loader :  Class loader, which is used to load proxy classes. You can use the loader of real objects
         * Class<?>[] interfaces :  The real object implements the same interface as the proxy object
         * InvocationHandler h  :  Call handler for proxy object
         */
        SellTickets sellTickets  = (SellTickets) Proxy.newProxyInstance(trainStation.getClass().getClassLoader(),
                trainStation.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**
                     *  InvocationHandler Parameter description of invoke method in:
                     * @param proxy   Proxy object
                     * @param method  Method instance corresponding to the interface method called on the proxy object
                     * @param args    The actual parameters passed when a proxy object calls an interface method
                     * @return    The return value of the interface when the proxy object calls the interface method
                     * @throws Throwable
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //Enhancement of proxy object routing method
                        System.out.println("The agent collects some lucky and bitter expenses( JDK)");
                        //Execute real objects
                        final Object invoke = method.invoke(trainStation, args);
                        return invoke;
                    }
                });
        return sellTickets;
    }
}

//test
public class Test {
    public static void main(String[] args) {
        //Create an object for the proxy factory
        ProxyFactory factory = new ProxyFactory();
        //Production agent object
        SellTickets proxyPoint = factory.getProxyPoint();

        proxyPoint.sell();
    }
}

Using dynamic agents, we consider the following questions:

  • Is ProxyFactory a proxy class?

    ProxyFactory is not the proxy class mentioned in the proxy mode, but the proxy class is a class dynamically generated in memory during the running process of the program. Check the structure of the proxy class through Alibaba's open source Java diagnostic tool (Arthas):

    package com.sun.proxy;
    
    import com.itheima.proxy.dynamic.jdk.SellTickets;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.lang.reflect.UndeclaredThrowableException;
    
    public final class $Proxy0 extends Proxy implements SellTickets {
        private static Method m1;
        private static Method m2;
        private static Method m3;
        private static Method m0;
    
        public $Proxy0(InvocationHandler invocationHandler) {
            super(invocationHandler);
        }
    
        static {
            try {
                m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
                m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
                m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]);
                m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
                return;
            }
            catch (NoSuchMethodException noSuchMethodException) {
                throw new NoSuchMethodError(noSuchMethodException.getMessage());
            }
            catch (ClassNotFoundException classNotFoundException) {
                throw new NoClassDefFoundError(classNotFoundException.getMessage());
            }
        }
    
        public final boolean equals(Object object) {
            try {
                return (Boolean)this.h.invoke(this, m1, new Object[]{object});
            }
            catch (Error | RuntimeException throwable) {
                throw throwable;
            }
            catch (Throwable throwable) {
                throw new UndeclaredThrowableException(throwable);
            }
        }
    
        public final String toString() {
            try {
                return (String)this.h.invoke(this, m2, null);
            }
            catch (Error | RuntimeException throwable) {
                throw throwable;
            }
            catch (Throwable throwable) {
                throw new UndeclaredThrowableException(throwable);
            }
        }
    
        public final int hashCode() {
            try {
                return (Integer)this.h.invoke(this, m0, null);
            }
            catch (Error | RuntimeException throwable) {
                throw throwable;
            }
            catch (Throwable throwable) {
                throw new UndeclaredThrowableException(throwable);
            }
        }
    
        public final void sell() {
            try {
                this.h.invoke(this, m3, null);
                return;
            }
            catch (Error | RuntimeException throwable) {
                throw throwable;
            }
            catch (Throwable throwable) {
                throw new UndeclaredThrowableException(throwable);
            }
        }
    }
    
    

    From the above class, we can see the following information:

    • The proxy class ($Proxy0) implements SellTickets, which proves that the real class and proxy class we mentioned earlier implement the same interface.
    • The proxy class ($Proxy0) passed the anonymous inner class object we provided to the parent class.
  • What is the execution process of dynamic agent?

    Here are the key codes extracted:

    //Agent class dynamically generated during program running
    public final class $Proxy0 extends Proxy implements SellTickets {
        private static Method m3;
    
        public $Proxy0(InvocationHandler invocationHandler) {
            super(invocationHandler);
        }
    
        static {
            m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]);
        }
    
        public final void sell() {
            this.h.invoke(this, m3, null);
        }
    }
    
    //Dynamic proxy related classes provided by Java
    public class Proxy implements java.io.Serializable {
    	protected InvocationHandler h;
    	 
    	protected Proxy(InvocationHandler h) {
            this.h = h;
        }
    }
    
    //Agent factory class
    public class ProxyFactory {
    
        private TrainStation station = new TrainStation();
    
        public SellTickets getProxyObject() {
            SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(station.getClass().getClassLoader(),
                    station.getClass().getInterfaces(),
                    new InvocationHandler() {
                        
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
                            System.out.println("The agency charges some service fees(JDK Dynamic agent mode)");
                            Object result = method.invoke(station, args);
                            return result;
                        }
                    });
            return sellTickets;
        }
    }
    
    
    //Test access class
    public class Client {
        public static void main(String[] args) {
            //Get proxy object
            ProxyFactory factory = new ProxyFactory();
            SellTickets proxyObject = factory.getProxyObject();
            proxyObject.sell();
        }
    }
    
    

The execution process is as follows:

1. Called through a proxy object in the test class sell()method
2. According to the characteristics of polymorphism, the execution is a proxy class( $Proxy0)Medium sell()method
3. Proxy class( $Proxy0)Medium sell()Method is called again InvocationHandler Implementation of subclass objects of interface invoke method
4. invoke Method executes the class to which the real object belongs through reflection(TrainStation)Medium sell()method

3.1.5 CGLIB dynamic agent

In the same case above, we use CGLIB proxy again.

If the SellTickets interface is not defined, only trainstation (railway station class) is defined. Obviously, JDK proxy cannot be used, because JDK dynamic proxy requires that interfaces must be defined and proxy interfaces.

CGLIB is a powerful and high-performance code generation package. It provides a proxy for classes that do not implement interfaces, and provides a good supplement to the dynamic proxy of JDK.

CGLIB is a package provided by a third party, so you need to import the coordinates of the jar package:

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

The proxy class is a subclass of the target class

The code is as follows:

//Ticket selling interface
public class TrainStation implements SellTickets {
    @Override
    public void sell() {
        System.out.println("Buy tickets at the railway station");
    }
}

//Proxy factory, used to create proxy objects
public class ProxyFactory implements MethodInterceptor {
    //Declare target object
    private TrainStation trainStation = new TrainStation();

    //Get proxy object
    public TrainStation getProxyPoint(){
        //Create an Enhancer object, which is similar to the Proxy class of JDK dynamic Proxy. The next step is to set several parameters
        Enhancer enhancer = new Enhancer();
        //Set parent bytecode object
        enhancer.setSuperclass(TrainStation.class);
        //Set callback function
        enhancer.setCallback(this);
        //Create proxy object
         TrainStation obj = (TrainStation) enhancer.create();
         return obj;
    }

    /**
     *intercept Method parameter description:
     * @param o          Proxy object
     * @param method     Method instances of methods in real objects
     * @param objects    Actual parameters
     * @param methodProxy  Method instance of the method in the proxy object
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("The agency charges some service fees(CGLIB Dynamic agent mode)");
        final Object invoke = method.invoke(trainStation,objects);
        return invoke;
    }
}


public class Test {
    public static void main(String[] args) {
        //Create an object for the proxy factory
        ProxyFactory factory = new ProxyFactory();
        //Get proxy object
        final TrainStation proxyPoint = factory.getProxyPoint();
        proxyPoint.sell();
    }
}

3.1. 6 Comparison of three agents

  • jdk agent and CGLIB agent

    CGLib is used to realize dynamic proxy. The bottom layer of CGLib adopts ASM bytecode generation framework and bytecode technology to generate proxy classes in jdk1 Before 6, it was more efficient than using Java reflection. The only thing to note is that CGLib cannot delegate classes or methods declared as final, because the principle of CGLib is to dynamically generate subclasses of the proxied class.

    At jdk1 6,JDK1.7,JDK1.8 after gradually optimizing the JDK dynamic agent, the JDK agent efficiency is higher than the cglib agent efficiency when the number of calls is small. Only when a large number of calls are made, jdk1 6 and jdk1 7 is a little less efficient than cglib agent, but to jdk1 8, the efficiency of JDK agent is higher than that of cglib agent. 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 methods declared in the interface are transferred to a centralized method of the calling processor (InvocationHandler.invoke). In this way, when there are a large number of interface methods, we can handle them flexibly without transferring each method like static proxy.

    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 this problem

3.1. 7 advantages and disadvantages

advantage:

  • Proxy mode plays an intermediary role 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 degree of the system to a certain extent;

Disadvantages:

  • It increases the complexity of the system;

3.1. 8 usage scenarios

  • 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 provided by the remote service without paying too much attention to the details of the communication part.

  • Firewall proxy

    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.

  • Protect or Access agent

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

3.2 adapter mode

3.2. 1 Overview

If you travel to European countries, their sockets are shown on the far left of the figure below, which is the European standard. The plug we use is shown on the far right in the figure below. Therefore, our laptops and mobile phones cannot be directly charged locally. Therefore, we need a socket converter. The first side of the converter is inserted into the local socket and the second side is for us to charge, so that our plug can be used locally. There are many examples in life, such as mobile phone charger (converting 220v to 5v voltage), card reader, etc. in fact, the adapter mode is used.

definition:

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

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.

3.2. 2 Structure

The Adapter pattern contains the following main roles:

  • Target interface: the interface expected by the current system business. It can be an abstract class or interface.
  • Adapter class: 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 a target interface by inheriting or referencing the adapter object, so that customers can access the adapter in the format of the target interface.

3.2. Type 3 adapter mode

Implementation method: define an adapter class to implement the business interface of the current system and inherit the existing components in the existing component library.

Card reader

An existing computer can only read the SD card, and to read the contents of the TF card, you need to use the adapter mode. Create a card reader to read the contents of the TF card.

//SD card interface
public interface SDCard {
    //Method of reading SD card
    String readSD();
    //Write SD card function
    void writeSD(String msg);
}

//SD card implementation class
public class SDCardImpl implements SDCard {
    public String readSD() {
        String msg = "sd card read a msg :hello word SD";
        return msg;
    }

    public void writeSD(String msg) {
        System.out.println("sd card write msg : " + msg);
    }
}

//Computer
public class Computer {

    public String readSD(SDCard sdCard) {
        if(sdCard == null) {
            throw new NullPointerException("sd card null");
        }
        return sdCard.readSD();
    }
}

//TF card interface
public interface TFCard {
    //Method of reading TF Card
    String readTF();
    //Write TF card function
    void writeTF(String msg);
}

//TF Card implementation class
public class TFCardImpl implements TFCard {

    public String readTF() {
        String msg ="tf card read msg : hello word tf card";
        return msg;
    }

    public void writeTF(String msg) {
        System.out.println("tf card write a msg : " + msg);
    }
}

//Define adapter class (SD compatible TF)
public class SDAdapterTF extends TFCardImpl implements SDCard {

    public String readSD() {
        System.out.println("adapter read tf card ");
        return readTF();
    }

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

//Test class
public class Client {
    public static void main(String[] args) {
        Computer computer = new Computer();
        SDCard sdCard = new SDCardImpl();
        System.out.println(computer.readSD(sdCard));

        System.out.println("------------");

        SDAdapterTF adapter = new SDAdapterTF();
        System.out.println(computer.readSD(adapter));
    }
}

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

3.2. 4 object adapter mode

Implementation method: the object adapter mode can introduce the implemented components in the existing component library into the adapter class, which also implements the business interface of the current system.

Card reader

We use the object adapter pattern to rewrite the reader case.

For the code of class adapter pattern, we only need to modify the adapter class (SDAdapterTF) and test class.

//Create adapter object (SD compatible TF)
public class SDAdapterTF  implements SDCard {

    private TFCard tfCard;

    public SDAdapterTF(TFCard tfCard) {
        this.tfCard = tfCard;
    }

    public String readSD() {
        System.out.println("adapter read tf card ");
        return tfCard.readTF();
    }

    public void writeSD(String msg) {
        System.out.println("adapter write tf card");
        tfCard.writeTF(msg);
    }
}

//Test class
public class Client {
    public static void main(String[] args) {
        Computer computer = new Computer();
        SDCard sdCard = new SDCardImpl();
        System.out.println(computer.readSD(sdCard));

        System.out.println("------------");

        TFCard tfCard = new TFCardImpl();
        SDAdapterTF adapter = new SDAdapterTF(tfCard);
        System.out.println(computer.readSD(adapter));
    }
}

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 Adapter to implement all the methods. At this time, we only need to inherit the abstract class.

3.2. 5 application scenarios

  • The previously developed system has classes that meet the functional requirements of the new system, but its interface is inconsistent with that of the new system.
  • Components provided by a third party are used, but the component interface definition is different from that required by itself.

3.2.6 JDK source code analysis

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

InputStreamReader inherits from Java The Reader in the IO package implements the abstract and unimplemented methods in it. For example:

public int read() throws IOException {
    return sd.read();
}

public int read(char cbuf[], int offset, int length) throws IOException {
    return sd.read(cbuf, offset, length);
}

sd (StreamDecoder class object) in the above code. In Sun's JDK implementation, the actual method implementation is the call encapsulation of the method with the same name of sun.nio.cs.StreamDecoder class.

  • InputStreamReader encapsulates the StreamDecoder that also implements the Reader.
  • StreamDecoder is not the content of Java SE API, but its own implementation given by Sun JDK. But we know that they encapsulate the byte stream class (InputStream) in the construction method, and decode and convert between byte stream and character stream through this class.

Conclusion:

From the surface level, InputStreamReader does the conversion from InputStream byte stream class to Reader character stream. As can be seen from the implementation class relationship structure in Sun JDK above, the design and implementation of StreamDecoder actually adopts the adapter mode.

3.4 bridging mode

3.4. 1 Overview

Now there is a requirement to create different graphics, and each graphics may have different colors. We can use inheritance to design class relationships:

We can find that there are many classes. If we add another shape or color, we need to create more classes.

Imagine that in a system with multiple dimensions that may change, inheritance will cause class explosion and inflexible expansion. Each time a specific implementation is added to a dimension, multiple subclasses must be added. In order to design the system more flexibly, we can consider using the bridge mode at this time.

definition:

Separate abstraction from implementation so that they can change independently. It is implemented by replacing inheritance relationship with composition relationship, which reduces the coupling between abstraction and implementation.

3.4. 2 Structure

The Bridge mode contains the following main roles:

  • Abstraction role: defines an abstract class and contains a reference to the implementation object.
  • Refined Abstraction role: it is a subclass of the abstract role, implements the business methods in the parent class, and calls the business methods in the abstract role through the composition relationship.
  • Implementer role: defines the interface of the implementer role, which can be called by the extended abstract role.
  • Concrete implementer role: give the concrete implementation of the implementation role interface.

3.4. 3 cases

[example] video player

It is necessary to develop a cross platform video player, which can play video files in various formats on different operating system platforms (such as Windows, Mac, Linux, etc.). The common video formats include RMVB, AVI, WMV, etc. the player contains two dimensions and is suitable for bridge mode.

The code is as follows:

/**
 * Operating system version
 * Abstract role
 */
public abstract class OperatingSystemVersion {
    protected VideFile videFile;

    public OperatingSystemVersion(VideFile videFile){
        this.videFile=videFile;
    }

    public abstract void VideoPlayback(String fileName);
}

//Extended abstract role
public class Linux extends OperatingSystemVersion{
    public Linux(VideFile videFile) {
        super(videFile);
    }

    @Override
    public void VideoPlayback(String fileName) {
        videFile.decode(fileName);
    }
}

public class Windows extends OperatingSystemVersion{


    public Windows(VideFile videFile) {
        super(videFile);
    }

    @Override
    public void VideoPlayback(String fileName) {
        videFile.decode(fileName);
    }
}

/**
 * video file 
 * Realize role
 */
public interface VideFile {
    void decode(String fileName);
}

//Concrete implementation role
public class AVIFile implements VideFile{
    @Override
    public void decode(String fileName) {
        System.out.println("AVI Format video file:"+fileName);
    }
}

public class MOVFile implements VideFile {
    @Override
    public void decode(String fileName) {
        System.out.println("MOV Format video file:"+fileName);
    }
}

//test
public class Test {
    public static void main(String[] args) {
        OperatingSystemVersion osv = new Windows(new MOVFile());
        osv.VideoPlayback("fierce fighting");
    }
}

3.4. 4 advantages

  • 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 class to implement the VideoFile interface, and other classes do not need to be changed.

  • Make details transparent to customers

3.4. 4 usage scenarios

  • 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, and make them establish an association relationship in the abstract level through bridging mode.

4. Behavioral model

The oriented 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

Among the above 11 behavior patterns, except that the template method pattern and Interpreter pattern are class behavior patterns, all the others belong to object behavior patterns.

4.1 template method mode

4.1. 1 Overview

In the process of object-oriented programming, programmers often encounter this situation: when designing a system, they know the key steps required by the algorithm and determine the execution order of these steps, but the specific implementation of some steps is still unknown, or the implementation of some steps is related to the specific environment.

definition:

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.

4.1. 2 Structure

The Template Method pattern contains the following main roles:

  • Abstract Class: it is 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: an abstract method is declared by an abstract class and implemented by its concrete subclasses.

      • 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: it has been implemented in abstract classes, including logical methods for judgment and empty methods that need to be overridden by subclasses.

        General hook methods are logical methods used for judgment. The name of such methods is usually isXxx, and the return value type is boolean.

  • Concrete Class: implement the abstract methods and hook methods defined in the abstract class. They are the constituent steps of top-level logic.

4.1. 3 case realization

Fried vegetables

The steps of cooking are fixed, which are divided into pouring oil, hot oil, pouring vegetables, pouring spices, stir frying and so on. Now use the template method pattern to simulate with code.

The code is as follows:

//abstract class
public abstract class AbstractClass {

    //Template method
    public final void cookProcess() {
        this.pourOil();
        this.heatOil();
        this.pourVegetable();
        this.pourSauce();
        this.fry();
    }


    //Specific method
    //Step 1: pour oil
    public void pourOil() {
        System.out.println("Pour oil");
    }

    //Step 2: the hot oil is the same, so it can be realized directly
    public void heatOil() {
        System.out.println("hot oil");
    }

    //Step 3: pouring vegetables is different
    public abstract void pourVegetable();

    //Step 4: pouring seasoning is different
    public abstract void pourSauce();


    //Step 5: stir fry is the same, so it can be realized directly
    public void fry(){
        System.out.println("Stir fry, cooked");
    }
}

//Specific subclass
public class ConcreteClass_BaoCai extends AbstractClass {
    @Override
    public void pourVegetable() {
        System.out.println("Add cabbage");
    }

    @Override
    public void pourSauce() {
        System.out.println("Add pepper");
    }
}

public class ConcreteClass_CaiXin extends AbstractClass {
    @Override
    public void pourVegetable() {
        System.out.println("Lower cabbage");
    }

    @Override
    public void pourSauce() {
        System.out.println("Add minced garlic");
    }
}
//test
public class Test {
    public static void main(String[] args) {
        //Stir fried cabbage with minced garlic
        ConcreteClass_CaiXin caiXin =  new ConcreteClass_CaiXin();
        caiXin.cookProcess();

        //Stir fried cabbage
        ConcreteClass_BaoCai baoCai = new ConcreteClass_BaoCai();
        baoCai.cookProcess();
    }
}

Note: in order to prevent malicious operations, the general template method adds the final keyword.

4.1. 4 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.

  • Reverse control is realized

    A parent class invokes the operation of its child class, extends different behaviors through the specific implementation of the child class, realizes the reverse control, and conforms to the 'opening and closing principle“

Disadvantages:

  • A subclass needs to be defined for each different implementation, which will increase the number of classes, make the system larger and the design more abstract.
  • 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.

4.1. 5 applicable scenarios

  • 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 whether a step in the parent algorithm is executed through the subclass to realize the reverse control of the subclass over the parent.

4.1.5 JDK source code analysis

The InputStream class uses the template method pattern. Multiple read() methods are defined in the InputStream class, as follows:

public abstract class InputStream implements Closeable {
    //Abstract method that requires subclasses to be overridden
    public abstract int read() throws IOException;

    public int read(byte b[]) throws IOException {
        return read(b, 0, b.length);
    }

    public int read(byte b[], int off, int len) throws IOException {
        if (b == null) {
            throw new NullPointerException();
        } else if (off < 0 || len < 0 || len > b.length - off) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return 0;
        }

        int c = read(); //The parameterless read method is called, which reads one byte of data at a time
        if (c == -1) {
            return -1;
        }
        b[off] = (byte)c;

        int i = 1;
        try {
            for (; i < len ; i++) {
                c = read();
                if (c == -1) {
                    break;
                }
                b[off + i] = (byte)c;
            }
        } catch (IOException ee) {
        }
        return i;
    }
}

As can be seen from the above code, the parameterless read() method is an abstract method, and subclasses must be implemented. The read(byte b []) method calls the read(byte b [], int off, int len) method, so the focus here is on the method with three parameters. read(byte b[], int off, int len) is the template method.

In lines 18 and 27 of this method, you can see that the parameterless Abstract read() method is called.

The summary is as follows: the method of reading a byte array data has been defined in the InputStream parent class. It is to read one byte at a time, store it in the first index position of the array, and read len bytes of data. How to read a byte of data? Implemented by subclasses.

4.2 observer mode

4.2. 1 Overview

definition:

Also known as Publish/Subscribe mode, it defines a one to many dependency that allows multiple observer objects to listen to a topic object at the same time. When the status of the topic object changes, it will notify all observer objects so that they can update themselves automatically.

4.2. 2 Structure

There are the following roles in observer mode:

  • Subject: abstract topic (Abstract observer). The abstract topic role saves all observer objects in a collection. Each topic can have 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: Abstract observer is an abstract class of observer. It defines an update interface to update itself when notified of 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.

4.4. 3 case realization

The official account of WeChat

When official account is official, we will have this experience. When new content is updated in your official account, it will be sent to WeChat users who are interested in the official account number. The WeChat public address is also available. We use the observer mode to simulate such scenes. WeChat users are observers. WeChat official account is the observer. Many WeChat users are concerned about the official account of program ape.

The code is as follows:

Defines an abstract topic class and provides three methods: add, delete and notify

//Abstract topic class
public interface Subject {
    //Add user
    void add(Observer observer);

    //delete user
    void delete(Observer observer);

    //Update method
    void notify(String msg);

}

The official account of WeChat is a specific topic (the specific observer), which stores WeChat subscribers subscribing to the official account and implements the method of abstract topics.

//Specific topic class
public class OAccounts implements Subject {
    //Official account of WeChat subscribers
    private List<Observer> users = new ArrayList<>();


    @Override
    public void add(Observer observer) {
        users.add(observer);
    }

    @Override
    public void delete(Observer observer) {
        users.remove(observer);
    }

    @Override
    public void notify(String msg) {
        for (Observer user : users) {
            user.update(msg);
        }
    }
}

Define the abstract Observer class, which defines an updated method

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

Define a specific Observer class. Wechat users are observers, which implements the update method

//Specific observer
public class WeChatUser implements Observer {
    private String name;

    public WeChatUser(String name) {
        this.name = name;
    }

    @Override
    public void update(String msg) {
        System.out.println(name+"--->"+msg);
    }
}

Test:

public class Demo {
    public static void main(String[] args) {
        OAccounts accounts = new OAccounts();
        accounts.add(new WeChatUser("Zhang San"));
        accounts.add(new WeChatUser("Li Si"));
        accounts.add(new WeChatUser("Wang Wu"));
        accounts.add(new WeChatUser("Yang Jian"));


        accounts.notify("I've posted new tweets. Have a look!");
    }
}

4.2. 4 advantages and disadvantages

advantage

  • It reduces the coupling relationship between the target and the observer, which is an abstract coupling relationship.
  • When the observed sends a notice, all registered observers will receive the information [broadcast mechanism can be realized]

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

4.2. 5 usage scenarios

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

Topics: Java Design Pattern