Principles of software design

1. Open and close principle

Definition: Software entities should be open for extension and closed for modification.

2. Richter’s substitution principle

Definition: Inheritance must guarantee that the properties of the superclass are still true in the subclasses. That is, when a subclass inherits its parent class, it should avoid rewriting the parent class method except adding new methods to add new functions, because this will lead to poor reusability of the entire inheritance system.

3. Dependency inversion principle

Definition: a high-level module should not depend on a low-level module; both should depend on its abstraction; Abstractions should not depend on details, details should depend on abstractions. The core idea is to program for interfaces rather than implementations, which reduces coupling, improves system stability, and improves code readability and maintainability.

4. Single responsibility principle

Definition: A class should have one and only one principle that causes it to change, otherwise the class should be split. The core idea is to control the granularity of classes and improve the cohesion of classes.

5. Interface isolation principle

Definition: A class’s dependency on another class should be based on the smallest interface. The core idea is to establish corresponding interfaces for each specific function, rather than trying to contain all functions in one interface, which not only ensures relative independence, but also avoids the bloated caused by too many interfaces.

6. Demeter’s Rule (Least Know Rule)

Definition: If two software entities do not need to communicate directly, they should avoid calling each other directly and instead forward the call through a third party to reduce coupling and ensure the relative independence of modules.

7. Principle of composite reuse (principle of composite reuse)

Definition: Reuse should be achieved using association relationships such as composition and aggregation before inheritance.

Conclusion: The open closed principle is the general principle, it tells us to be open to expansion, to modify closed; Richter’s substitution tells us not to break the inheritance system; The dependency inversion principle tells us to program for interfaces; The single responsibility principle tells us to implement a class with a single responsibility; The principle of interface isolation tells us to keep interfaces simple and simple when designing them. Demeter’s rule tells us to reduce coupling; The principle of composite reuse tells us to use composite or aggregate relation reuse in preference to inheritance relation reuse.

Create a type

1. Singleton mode

1.1 Hangry singleton

The hunchman singleton is the simplest singleton pattern. It creates the related singleton object when the class is initialized. It can be implemented as a static code block or as a static inner class:

Static code block:

public class HungrySingleton implements Serializable {

    private static final HungrySingleton instance;

    static {
        instance = new HungrySingleton();
    }

    // Make sure the constructor is private
    private HungrySingleton(a) {}

    // Get the singleton
    public static HungrySingleton getInstance(a) {
        returninstance; }}Copy the code

Static inner class mode:

public class StaticInnerClassHungrySingleton {

    private static class InnerClass {
        private static StaticInnerClassHungrySingleton instance = new StaticInnerClassHungrySingleton();
    }

    // Make sure the constructor is private
    private StaticInnerClassHungrySingleton(a) {}

    // Get the singleton
    public static StaticInnerClassHungrySingleton getInstance(a) {
        returnInnerClass.instance; }}Copy the code

The advantage of hanky-hank singleton is that it does not have thread-safety problems, and the uniqueness of objects is guaranteed by the virtual machine during class initialization. The disadvantage is that it can be a waste of resources if object creation is expensive and singletons are not necessarily used.

1.2 Lazy singleton

The idea of lazy singletons is to create singletons only when they are needed, return them if they exist and return after they are created if they don’t exist, as shown in the following example:

public class LazySingletonUnsafe {

    private static LazySingletonUnsafe instance = null;

    private LazySingletonUnsafe(a) {}public static LazySingletonUnsafe getInstance(a) {
        if (instance == null) {
            instance = new LazySingletonUnsafe();
        }
        returninstance; }}Copy the code

Note that the above code is fine in a single-threaded environment, but is thread-unsafe in a multi-threaded environment because the following creation code is non-atomic:

if (instance == null) {
    instance = new LazySingletonUnsafe();
}
Copy the code

To ensure atomicity of the creation operation, use the synchronized keyword:

public synchronized static LazySingletonUnsafe getInstance(a) {
        if (instance == null) {
            instance = new LazySingletonUnsafe();
        }
        return instance;
    }
Copy the code

The method is thread-safe at this point, but there are performance issues. Because synchronized modifies static methods, it locks the entire class object, which means that any thread that wants to acquire that singleton must wait for the internal lock to be released. Assuming that the singleton has been created and 100 threads concurrently acquire the singleton, all 100 threads will have to wait. This obviously reduces the throughput of the system, so it is better to implement lazy singleton with double-checked locking:

public class DoubleCheckLazySingletonSafe {

    // Use volatile to disable instruction reordering
    private static volatile DoubleCheckLazySingletonSafe instance = null;

    private DoubleCheckLazySingletonSafe(a) {}// Double check
    public static DoubleCheckLazySingletonSafe getInstance(a) {
        if (instance == null) {
            synchronized (DoubleCheckLazySingletonSafe.class) {
                if (instance == null) {
                    instance = newDoubleCheckLazySingletonSafe(); }}}returninstance; }}Copy the code

If the singleton object is created and 100 threads concurrently acquire it, instance == null must be false, so all threads will directly acquire the singleton instead of entering the synchronized code block. This reduces the locking scope of the lock, resulting in better performance with a smaller lock granularity. However, the internal if code block still needs to be modified with the synchronized keyword to ensure the atomicity of the entire IF code block.

It is important to note that instance here requires the volatile key modifier, which prevents instruction reordering during object creation. There are usually three steps to create an object:

  1. Allocate memory for objects;
  2. Call the constructor method of the object and initialize it;
  3. Point the variable to the corresponding memory address.

If the instruction reorder is not prohibited, the instruction reorder may occur in steps 2 and 3, which is not a problem in a single thread, and also in accordance with the as-if-serial principle, but in a multi-thread will be thread unsafe problem:

// 2. Since thread 1 has already pointed the variable to the memory address, other threads decide that instance is not empty and get it directly, but instance may not be initialized yet
if (instance == null) { 
    synchronized (DoubleCheckLazySingletonSafe.class) {
        if (instance == null) {
            // 1. Assume that thread 1 has allocated memory to the object and the variable instance points to the corresponding memory address, but has not completed initialization
            instance = newDoubleCheckLazySingletonSafe(); }}}return instance;
Copy the code

Due to the existence of reorder, other threads may get an instance that has not been initialized, which may lead to an exception, so the instruction reorder should be prohibited.

1.3 Using serialization to break singletons

Both hunchman singletons and double-checked lock lazy singletons are thread-safe and can meet everyday development needs, but if you are a library developer, you need to consider singletons’ writing-safe patterns in order to prevent the singletons in your library from being broken, either intentionally or accidentally, when called. Serialization and reflection attacks are two common ways to destroy singletons. Examples are as follows:

public class SerializationDamage {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        HungrySingleton instance = HungrySingleton.getInstance();
        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("SingletonFile"));
        outputStream.writeObject(instance);
        ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(new File("SingletonFile")));
        HungrySingleton newInstance = (HungrySingleton) inputStream.readObject();
        System.out.println(instance == newInstance); // false}}Copy the code

After implementing the Serializable interface with HungrySingleton, you can use the above code to serialize the object to a file and then de-serialize it. You can find that the object is not an object twice, which means that the singleton pattern is broken by serialization and deserialization. To solve this problem, define the readResolve() method in the corresponding singleton class:

public class HungrySingleton implements Serializable {...private Object readResolve(a) {
        returninstance; }... }Copy the code

This method is called at deserialization time to return a singleton object. The ObjectInputStream class has the following source code: ObjectInputStream

  In this use case, the readObject internally ends up calling the readOrdinaryObject method
private Object readOrdinaryObject(boolean unshared) throws IOException{...if(obj ! =null && handles.lookupException(passHandle) == null &&
            desc.hasReadResolveMethod()) // If the corresponding object has a readResolve method
        {
            // The method is called through reflection to get the corresponding singletonObject rep = desc.invokeReadResolve(obj); . handles.setObject(passHandle, obj = rep); }return obj;
    }
Copy the code

1.4 Use reflection to destroy singletons

Singleton patterns can also be broken using reflection, and because Java’s reflection is so powerful, this is almost impossible to avoid, as shown in the following example:

public class ReflectionDamage {
    public static void main(String[] args) throws Exception {
        Constructor<HungrySingleton> constructor = HungrySingleton.class.getDeclaredConstructor();
        // Get access to the private constructor
        constructor.setAccessible(true);
        HungrySingleton hungrySingleton = constructor.newInstance();
        HungrySingleton instance = HungrySingleton.getInstance();
        System.out.println(hungrySingleton == instance); // false}}Copy the code

Even if the constructor is declared private when the singleton is created, permissions can still be obtained by reflection modification, and the singleton pattern is broken. If you’re using hunchman singletons, you can circumvent this by writing code like this:

public class HungrySingleton implements Serializable {

    private static final HungrySingleton instance;

    static {
        instance = new HungrySingleton();
    }

    Since Instance is initialized when the class is created, a custom RuntimeException is thrown when the constructor is called using reflection
    private HungrySingleton(a) {
        if(instance ! =null) {
            throw new RuntimeException("Singleton mode disallows reflection calls"); }}... }Copy the code

If you are using lazy singletons, there is no way to prevent reflection attacks because there is no way to know when the object will be created, and reflection can gain access to any field, method, or constructor.

Is there a singleton that can be thread-safe and prevent serialization and reflection? In the Java language, this can be done through enumerated singletons.

1.5 Enumerated singleton

An example of a singleton implementation using enumeration is as follows:

public enum EnumInstance {

    INSTANCE;

    private String field;

    public String getField(a) {
        return field;
    }

    public void setField(String field) {
        this.field = field;
    }

    public static EnumInstance getInstance(a) {
        returnINSTANCE; }}Copy the code

To implement a singleton enumeration, the corresponding singleton class must be qualified with enum, and the rest of the field declarations (e.g., field) and method declarations (e.g., setField) are the same as normal classes. First, enumerating classes is thread-safe. This can be verified by using Jad to decompile the class file:

Decomcompiled by Jad V1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name: EnumInstance.java

package com.heibaiying.creational.singleton;

// Immutable class
public final class EnumInstance extends Enum
{

    public static EnumInstance[] values()
    {
        return (EnumInstance[])$VALUES.clone();
    }

    public static EnumInstance valueOf(String name)
    {
        return (EnumInstance)Enum.valueOf(com/heibaiying/creational/singleton/EnumInstance, name);
    }

    Enum(String name, int ordinal) constructor is defined only in Enum
    private EnumInstance(String s, int i)
    {
        super(s, i);
    }

    // Custom methods
    public String getField(a)
    {
        return field;
    }

    public void setField(String field)
    {
        this.field = field;
    }

    public static EnumInstance getInstance(a)
    {
        return INSTANCE;
    }

    // Static immutable instance object
    public static final EnumInstance INSTANCE;
    // Customize the field
    private String field;
    private static final EnumInstance $VALUES[];

    // Initialize in static code
    static 
    {
        INSTANCE = new EnumInstance("INSTANCE".0);
        $VALUES = (newEnumInstance[] { INSTANCE }); }}Copy the code

The decompiler tool shows that it is similar to the Hunchman singleton pattern, so it is also thread-safe. It also protects against serialization and reflection attacks:

public class EnumInstanceTest {
    public static void main(String[] args) throws Exception {
        // Serialization attack
        EnumInstance instance = EnumInstance.getInstance();
        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("EnumSingletonFile"));
        outputStream.writeObject(instance);
        ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(new File("EnumSingletonFile")));
        EnumInstance newInstance = (EnumInstance) inputStream.readObject();
        System.out.println(instance == newInstance);
        // Reflection attack, Enum class has only one two-parameter constructor: Enum(String name, int ordinal)
        Constructor<EnumInstance> constructor = EnumInstance.class.getDeclaredConstructor(String.class, int.class);
        constructor.setAccessible(true);
        EnumInstance enumInstance = constructor.newInstance("name".0); System.out.println(instance == enumInstance); }}Copy the code

For serialization and deserialization, the enumeration singleton ensures that you get the same instance twice. For reflection attacks, the enumeration class singleton throws an explicit exception:

Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
    at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
    at com.heibaiying.creational.singleton.EnumInstanceTest.main(EnumInstanceTest.java:18)
Copy the code

2. Simple Factory mode

2.1 define

For the caller, it doesn’t need to know the creation details of the object. It just needs to tell the factory the type of the object it wants, which then automatically creates and returns it.

2.2 the sample

Product Abstract class:

public abstract class Phone {
    public abstract void call(String phoneNum);
}
Copy the code

Specific products:

public class HuaweiPhone extends Phone {
    public void call(String phoneNum) {
        System.out.println("Huawei mobile phone call:"+ phoneNum); }}Copy the code
public class XiaomiPhone extends Phone {
    public void call(String phoneNum) {
        System.out.println("Call from Xiaomi mobile phone:"+ phoneNum); }}Copy the code

Mobile Phone Factory:

public class PhoneFactory {

    public Phone getPhone(String type) {
        if ("xiaomi".equalsIgnoreCase(type)) {
            return new XiaomiPhone();
        } else if ("huawei".equalsIgnoreCase(type)) {
            return new HuaweiPhone();
        }
        return null; }}Copy the code

Call the factory class to get a concrete instance:

public class ZTest {
    public static void main(String[] args) {
        PhoneFactory phoneFactory = new PhoneFactory();
        phoneFactory.getPhone("xiaomi").call("123");
        phoneFactory.getPhone("huawei").call("321"); }}Copy the code

2.3 the advantages and disadvantages

The advantage of a simple factory is that it shields the object creation process from the user, freeing the user from the creation details. The disadvantage is that it violates the open and close principle. In the simple factory model, if you want to add new products, you need to modify the judgment logic in the simple factory, which violates the open and closed principle and therefore does not belong to the GOF classic 23 design patterns. In the Java language, this flaw can be circumvented by generics, in which case the method of creating the product can be modified as follows:

public Phone getPhone(Class<? extends Phone> phoneClass) {
    try {
        return (Phone) Class.forName(phoneClass.getName()).newInstance();
    } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
        e.printStackTrace();
    }
    return null;
}
Copy the code

3. Factory mode

3.1 define

Defines a factory interface for creating objects, but it is up to subclasses to decide which factory to instantiate.

3.2 the sample

Product Abstract class:

public abstract class Phone {
    public abstract void call(String phoneNum);
}
Copy the code

Product realization class:

public class HuaweiPhone extends Phone {
    public void call(String phoneNum) {
        System.out.println("Huawei mobile phone call:"+ phoneNum); }}Copy the code
public class XiaomiPhone extends Phone {
    public void call(String phoneNum) {
        System.out.println("Call from Xiaomi mobile phone:"+ phoneNum); }}Copy the code

Factory interface:

public interface Factory {
    Phone produce(a);
}
Copy the code

Factory implementation class:

public class HuaweiPhoneFactory implements Factory {
    @Override
    public Phone produce(a) {
        return newHuaweiPhone(); }}Copy the code
public class XiaomiPhoneFactory implements Factory {
    @Override
    public Phone produce(a) {
        return newXiaomiPhone(); }}Copy the code

It is up to the caller to decide which factory object to instantiate:

public class ZTest {
    public static void main(String[] args) {
        XiaomiPhoneFactory xiaomiPhoneFactory = new XiaomiPhoneFactory();
        xiaomiPhoneFactory.produce().call("123");
        HuaweiPhoneFactory huaweiPhoneFactory = new HuaweiPhoneFactory();
        huaweiPhoneFactory.produce().call("456"); }}Copy the code

3.3 the advantages

The advantage of factory mode lies in its good encapsulation and expansibility. If you want to add new products (such as OppoPhone), you only need to add corresponding factory classes. At the same time, just like simple factory, it also shields irrelevant details from users and reduces the coupling degree of the system.

4. Abstract factory pattern

4.1 define

Provides an interface for creating a series of related or interdependent objects without specifying their concrete implementation classes. The Abstract factory pattern is an updated version of the factory pattern and is suitable for situations where there are multiple products. Following the example above, suppose that each factory not only produces mobile phones, but also needs to produce the corresponding chargers to be considered a saleable product. The code example is as follows:

4.2 the sample

Charger Abstract class:

public abstract class Charger {
    public abstract void Charge(Phone phone);
}
Copy the code

Charger implementation class:

public class HuaiweiCharger extends Charger {
    @Override
    public void Charge(Phone phone) {
        System.out.println("Huawei charger to" + phone + "Charging"); }}Copy the code
public class XiaomiCharger extends Charger {
    @Override
    public void Charge(Phone phone) {
        System.out.println("Xiaomi Charger for" + phone + "Charging"); }}Copy the code

Factory interface:

public interface Factory {
    Phone producePhone(a);
    Charger produceCharger(a);
}
Copy the code

Factory implementation class:

public class HuaweiPhoneFactory implements Factory {
    @Override
    public Phone producePhone(a) {
        return new HuaweiPhone();
    }
    @Override
    public Charger produceCharger(a) {
        return newHuaiweiCharger(); }}Copy the code
public class XiaomiPhoneFactory implements Factory {
    @Override
    public Phone producePhone(a) {
        return new XiaomiPhone();
    }
    @Override
    public Charger produceCharger(a) {
        return newXiaomiCharger(); }}Copy the code

Invoke the concrete factory implementation class:

public class ZTest {
    public static void main(String[] args) {
        XiaomiPhoneFactory xiaomiPhoneFactory = new XiaomiPhoneFactory();
        xiaomiPhoneFactory.produceCharger().Charge(xiaomiPhoneFactory.producePhone());
        HuaweiPhoneFactory huaweiPhoneFactory = newHuaweiPhoneFactory(); huaweiPhoneFactory.produceCharger().Charge(huaweiPhoneFactory.producePhone()); }}Copy the code

4.3 the advantages and disadvantages

The abstract factory pattern inherited the advantages of the factory pattern, can be used for multiple products, but the corresponding product family must be relatively fixed, suppose that we now think that mobile phone + charger + headset is a product can be sold, are all factory need to change the above, but obviously not all phones have form a complete set of headphones, The product family of mobile phone + charger is relatively fixed.

5. The Builder pattern

5.1 define

Separating the construction of a complex object from its representation enables the same construction process to create different representations. It breaks down the creation of a complex object into simple steps that are then assembled step by step.

5.2 the sample

Product entity category:

public class Phone {
    /* Processor */
    private String processor;
    /* Camera */
    private String camera;
    / * * / screen
    private String screen;
}
Copy the code

Builder Abstract class:

public abstract class Builder {

    protected Phone phone = new Phone();
    /* Install the processor */
    public abstract void addProcessor(a);
    /* Assemble the camera */
    public abstract void addCamera(a);
    /* Install screen */
    public abstract void addScreen(a);
  
    public Phone produce(a) {
        returnphone; }}Copy the code

Builder implementation class:

public class HuaweiBuilder extends Builder {
    @Override
    public void addProcessor(a) {
        phone.setProcessor("Hisi Kirin Processor");
    }

    @Override
    public void addCamera(a) {
        phone.setCamera(Leica CAM.);
    }

    @Override
    public void addScreen(a) {
        phone.setScreen("OLED"); }}Copy the code
public class XiaomiBuilder extends Builder {
    @Override
    public void addProcessor(a) {
        phone.setProcessor(Qualcomm Snapdragon Processor);
    }

    @Override
    public void addCamera(a) {
        phone.setCamera("SONY Camera");
    }

    @Override
    public void addScreen(a) {
        phone.setScreen("OLED"); }}Copy the code

Define a manager class (also known as a director class) that drives specific builders through the build process in a specified order:

public class Manager {

    private Builder builder;

    public Manager(Builder builder) {
        this.builder = builder;
    }

    public Phone buy(a) {
        builder.addCamera();
        builder.addProcessor();
        builder.addScreen();
        returnbuilder.produce(); }}Copy the code

Call the manager class to get the product:

public class ZTest {
    public static void main(String[] args) {
        Phone huawei = new Manager(new HuaweiBuilder()).buy();
        System.out.println(huawei);
        Phone xiaomi = new Manager(newXiaomiBuilder()).buy(); System.out.println(xiaomi); }}/ / output:(Processor = Qualcomm Snapdragon processor, Camera = SONY camera, screen=OLED)Copy the code

5.3 the advantages

The advantage of the builder pattern is that the complex build process is divided into multiple independent units, which ensures good encapsulation while ensuring extensibility, so that the client does not have to know the specific creation process of the product.

6. Prototyping

6.1 define

Specify what kind of objects to create with prototype instances, and create new objects by copying these prototypes.

6.2 the sample

In the Java language, the clone() method is used to implement the stereotype pattern:

public class Phone implements Cloneable {

    private String type;

    Phone(String type) {
        System.out.println("Constructor called");
        this.type = type;
    }

    public void call(a) {
        System.out.println(type + "Make a phone call");
    }

    @Override
    protected Object clone(a) throws CloneNotSupportedException {
        System.out.println("Clone method called");
        return super.clone(); }}Copy the code

Use clones to create objects:

Phone phone = new Phone("3 g mobile phone");
Phone clonePhone = (Phone) phone.clone();
clonePhone.call();
Copy the code

When using the Clone method, pay attention to distinguish between deep and shallow copies. That is, if the object to be copied contains variables of reference type, it also needs to be copied, as shown in the following example:

public class SmartPhone implements Cloneable {

    private String type;
    private Date productionDate;

    SmartPhone(String type, Date productionDate) {
        this.type = type;
        this.productionDate = productionDate;
    }

    public void call(a) {
        System.out.println(type + "Make a phone call");
    }

    @Override
    protected Object clone(a) throws CloneNotSupportedException {
        SmartPhone smartPhone = (SmartPhone) super.clone();
        // Copy the referenced object
        smartPhone.productionDate = (Date) smartPhone.productionDate.clone();
        returnsmartPhone; }}Copy the code

6.3 Application Scenarios

The prototype mode copies the binary stream directly in memory, and the constructor of the copied object is not executed, so performance is excellent. If the creation of objects is very resource-intensive, you should consider using the prototype pattern.

structured

1. Proxy mode

1.1 define

A proxy object is provided to the target object to control access to it by the external environment, which should access the proxy object, not the target object. Through the proxy pattern, functionality can be extended without changing the target object.

In Java language, according to the different generation time point of proxy object can be divided into static proxy and dynamic proxy, among which dynamic proxy can be divided into JDK proxy and Cglib proxy according to the different implementation mode.

1.2 Static Proxy

The proxy object and the target object need to implement the same interface:

public interface IService {
    void compute(a);
}
Copy the code

Target audience:

public class ComputeService implements IService {
    @Override
    public void compute(a) {
        System.out.println("Business processing"); }}Copy the code

Inject an instance of the target object into the proxy object:

public class ProxyService implements IService {

    private IService target;

    public ProxyService(IService target) {
        this.target = target;
    }

    @Override
    public void compute(a) {
        System.out.println("Permission Verification");
        target.compute();
        System.out.println("Resource Recovery"); }}Copy the code

The call should access the proxy object, not the target object:

ProxyService proxyService = new ProxyService(new ComputeService());
proxyService.compute();
Copy the code

1.3 JDK agent

In addition to using static proxies, you can use the Proxy class and reflection functionality in the JDK to Proxy a target object:

ComputeService target = new ComputeService();
IService proxyInstance = (IService) Proxy.newProxyInstance(
    target.getClass().getClassLoader(),
    target.getClass().getInterfaces(), // List of interfaces to be implemented by proxy classes
    (proxy, method, args1) -> {
        System.out.println("Permission Verification");
        Object invoke = method.invoke(target, args1);
        System.out.println("Resource Recovery");
        return invoke;
    });
proxyInstance.compute();
Copy the code

Both static proxy and JDK dynamic proxy require that the target object must implement one or more interfaces. If the target object does not have any interfaces, you can use Cglib to proxy it.

1.4 additional agent

To use the Cglib proxy, you must import the associated dependencies:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>
Copy the code

The target object does not need to implement any interface:

public class ComputeService {
    public void compute(a) {
        System.out.println("Business processing"); }}Copy the code

Proxy using Cglib:

public class Proxy implements MethodInterceptor {

    private Object target;

    public Proxy(Object target) {
        this.target = target;
    }

    public Object getProxyInstance(a) {
        // Create a utility class that generates dynamic subclasses
        Enhancer enhancer = new Enhancer();
        // Specify the parent of the dynamically generated class
        enhancer.setSuperclass(target.getClass());
        // Set the callback
        enhancer.setCallback(this);
        // Dynamically generate subclasses
        return enhancer.create();
    }

   /** * we only need to implement the interception logic here, the rest of the code is relatively fixed */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws InvocationTargetException, IllegalAccessException {
        System.out.println("Permission Verification");
        Object invoke = method.invoke(target, args);
        System.out.println("Resource Recovery");
        returninvoke; }}Copy the code

Accessing proxy objects:

Proxy proxy = new Proxy(new ComputeService());
ComputeService service = (ComputeService) proxy.getProxyInstance();
service.compute();
Copy the code

2. Adapter mode

2.1 define

Translate the interface of one class into another interface that the customer wants so that classes that would otherwise not work together due to interface incompatibilities can work together.

2.2 the sample

Convert the 220V current into the corresponding specification current to charge the mobile phone through the adapter:

Power supply:

public class PowerSupply {

    private final int output = 220;

    public int output220V(a) {
        System.out.println("Power supply voltage:" + output);
        returnoutput; }}Copy the code

Voltage specification of mobile phone:

public interface Target {
    int output5V(a);
}
Copy the code

The adapter needs to inherit from the source class and implement the target class interface:

public class ChargerAdapter extends PowerSupply implements Target {
    @Override
    public int output5V(a) {
        int output = output220V();
        System.out.println("Charging head adapter conversion");
        output = output / 44;
        System.out.println("Output voltage:" + output);
        returnoutput; }}Copy the code

Testing:

public class ZTest {
    public static void main(String[] args) {
        Target target = newChargerAdapter(); target.output5V(); }}/ / output:Supply voltage:220Charging head adaption conversion output voltage:5
Copy the code

3. Bridge mode

3.1 define

Separate the abstract part from its implementation part so that they can all change independently. It reduces the coupling of abstraction and implementation of these two variable dimensions by using composition relations instead of inheritance relations.

3.2 the advantages

  • Separation of abstraction and implementation;
  • Excellent ability to expand;
  • Implementation details are transparent to clients, who can implement different requirements through various aggregations.

3.3 the sample

The separation of shapes and colors of a figure so that different effects can be achieved by combination:

Color abstraction and implementation:

public interface Color {
    String getDesc(a);
}

public class Blue implements Color {
    @Override
    public String getDesc(a) {
        return "Blue"; }}public class Red implements Color {
    @Override
    public String getDesc(a) {
        return "Red"; }}public class Yellow implements Color {
    @Override
    public String getDesc(a) {
        return "Yellow"; }}Copy the code

Graphical abstraction and implementation:

public abstract class Shape {

    private Color color;

    public Shape setColor(Color color) {
        this.color = color;
        return this;
    }

    public Color getColor(a) {
        return color;
    }

    public abstract void getDesc(a);
}


public class Round extends Shape {
    @Override
    public void getDesc(a) {
        System.out.println(getColor().getDesc() + "Circular"); }}public class Square extends Shape {
    @Override
    public void getDesc(a) {
        System.out.println(getColor().getDesc() + "Square"); }}Copy the code

Call by aggregation:

new Square().setColor(new Red()).getDesc();
new Square().setColor(new Blue()).getDesc();
new Round().setColor(new Blue()).getDesc();
new Round().setColor(new Yellow()).getDesc();
Copy the code

4. Combination mode

4.1 define

By combining objects into a tree structure to represent a partial-whole hierarchy, the composition pattern enables consistent use of individual objects and composite objects.

4.2 the advantages

  • The consistency of single objects and composite objects simplifies client code;

  • You can add new objects to a composite class without changing its source code.

4.3 the sample

Emulating a Linux file system:

Component classes that define all operations for folders and files:

public abstract class Component {

    private String name;

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

    public void add(Component component) {
        throw new UnsupportedOperationException("Add operation not supported");
    }

    public void remove(Component component) {
        throw new UnsupportedOperationException("Delete operation not supported");
    }


    public void vim(String content) {
        throw new UnsupportedOperationException("Opening with vim editor is not supported");
    }

    public void cat(a) {
        throw new UnsupportedOperationException("View operation not supported");
    }

    public void print(a) {
        throw new UnsupportedOperationException("Print operation not supported"); }}Copy the code

Folder class:

public class Folder extends Component {

    private List<Component> componentList = new ArrayList<>();

    public Folder(String name) {
        super(name);
    }

    @Override
    public void add(Component component) {
        componentList.add(component);
    }

    @Override
    public void remove(Component component) {
        componentList.remove(component);
    }

    @Override
    public void print(a) {
        System.out.println(getName());
        componentList.forEach(x -> System.out.println(""+ x.getName())); }}Copy the code

File types:

public class File extends Component {

    private String content;

    public File(String name) {
        super(name);
    }

    @Override
    public void vim(String content) {
        this.content = content;
    }

    @Override
    public void cat(a) {
        System.out.println(content);
    }

    @Override
    public void print(a) { System.out.println(getName()); }}Copy the code

Hierarchy is achieved through composition:

Folder rootDir = new Folder("ROOT");
Folder nginx = new Folder("Nginx Installation directory");
Folder tomcat = new Folder("Tomcat Installation Directory");
File startup = new File("startup.bat");
rootDir.add(nginx);
rootDir.add(tomcat);
rootDir.add(startup);
rootDir.print();
startup.vim("java -jar");
startup.cat();
nginx.cat();
Copy the code

5. Decoration mode

5.1 define

Dynamically add responsibilities or functions to an object without changing the structure of the existing object.

5.2 the advantages

  • Extending the functionality of objects using decorator patterns is more flexible than using inheritance.
  • You can create combinations of different behaviors by designing different decoration classes.

5.3 the sample

After you buy your phone, you may also buy a screen protector, a case, etc to decorate it:

Mobile phone Abstract Class and its implementation:

public abstract class Phone {
    public abstract int getPrice(a);
    public abstract String  getDesc(a);
}

public class MiPhone extends Phone {
    
    @Override
    public int getPrice(a) {
        return 1999;
    }
    
    @Override
    public String getDesc(a) {
        return "MiPhone"; }}Copy the code

Decorator abstract class:

public abstract class Decorator extends Phone {

    private Phone phone;

    public Decorator(Phone phone) {
        this.phone = phone;
    }

    @Override
    public int getPrice(a) {
        return phone.getPrice();
    }

    @Override
    public String getDesc(a) {
        returnphone.getDesc(); }}Copy the code

Mobile phone case decorator:

public class ShellDecorator extends Decorator {

    public ShellDecorator(Phone phone) {
        super(phone);
    }

    @Override
    public int getPrice(a) {
        return super.getPrice() + 200;
    }

    @Override
    public String getDesc(a) {
        return super.getDesc() + "+ phone case"; }}Copy the code

Screen protector decorator:

public class FilmDecorator extends Decorator {

    public FilmDecorator(Phone phone) {
        super(phone);
    }

    @Override
    public int getPrice(a) {
        return super.getPrice() + 100;
    }

    @Override
    public String getDesc(a) {
        return super.getDesc() + "+ toughened film"; }}Copy the code

Call the decorator to decorate the target object:

public class ZTest {
    public static void main(String[] args) {
        ShellDecorator decorator = new ShellDecorator(new FilmDecorator(new MiPhone()));
        System.out.println(decorator.getDesc() + ":"+ decorator.getPrice()); }}// Output: MiPhone + toughened film + phone case: 2299
Copy the code

6. Appearance mode

6.1 define

Under the current popular microservice architecture mode, we usually split a large system into multiple independent services. At this time, we need to provide a consistent interface to call the external system, which is a manifestation of the appearance mode.

6.2 the advantages

  • The coupling degree between subsystem and client is reduced.
  • The client is shielded from implementation details inside the system.

6.3 the sample

In order to imitate e-commerce shopping, the payment subsystem, warehousing subsystem and logistics subsystem need to be called internally, while these details are shielded to users:

Safety inspection system:

public class EnvInspectionService {
    public boolean evInspection(a) {
        System.out.println("Payment environment check...");
        return true; }}Copy the code

Payment subsystem:

public class AccountService {
    public boolean balanceCheck(a) {
        System.out.println("Checking account balance...");
        return true; }}Copy the code

Logistics subsystem:

public class LogisticsService {
    public void ship(Phone phone) {
        System.out.println(phone.getName() + "Shipped, please note check..."); }}Copy the code

Ordering system (appearance) :

public class OrderService {

    private EnvInspectionService inspectionService = new EnvInspectionService();
    private AccountService accountService = new AccountService();
    private LogisticsService logisticsService = new LogisticsService();

    public void order(Phone phone) {
        if (inspectionService.evInspection()) {
            if (accountService.balanceCheck()) {
                System.out.println("Payment successful"); logisticsService.ship(phone); }}}}Copy the code

The user only needs to access the facade and call the ordering interface:

Phone phone = new Phone(Mobile phone "XXX");
OrderService orderService = new OrderService();
orderService.order(phone);

/ / output:Payment environment check... Checking account balance... XXX mobile phone has been delivered, please note to check...Copy the code

7. Enjoy yuan mode

7.1 define

The use of sharing technology to effectively support the reuse of a large number of fine-grained objects, thread pool, caching technology are its representative implementation. There are two states in the share mode:

  • Internal state, which does not change as the environment changes, is determined at object initialization;
  • External state refers to the state that can be changed as the environment changes.

Using the share mode, you can avoid creating a large number of duplicate objects in the system, thus saving the system memory space.

7.2 the sample

Here is an example of creating a PPT template. Do not create a PPT template of the same type repeatedly:

PPT abstract class:

public abstract class PowerPoint {
    / * * / copyright
    private String copyright;
    private String title;

    /* The copyright information here is an internal state that is determined when the PPT object is first created */
    public PowerPoint(String copyright) {
        this.copyright = copyright;
    }

    /* The PPT title is an external state that can be changed by the external environment according to different requirements */
    public void setTitle(String title) {
        this.title = title;
    }

    abstract void create(a);

    @Override
    public String toString(a) {
        return "No. :" + hashCode() + ": PowerPoint{" +
            "copyright='" + copyright + '\' ' +
            ", title='" + title + '\' ' +
            '} '; }}Copy the code

PPT implementation class:

public class BusinessPPT extends PowerPoint {
    public BusinessPPT(String copyright) {
        super(copyright);
    }
    @Override
    void create(a) {
        System.out.println("Business PPT Template"); }}public class SciencePPT extends PowerPoint {
    public SciencePPT(String copyright) {
        super(copyright);
    }
    @Override
    void create(a) {
        System.out.println("Science and Technology PPT template"); }}public class ArtPPT extends PowerPoint {
    public ArtPPT(String copyright) {
        super(copyright);
    }
    @Override
    void create(a) {
        System.out.println("Art PPT Template"); }}Copy the code

Create and share through the factory pattern:

public class PPTFactory {

    private HashMap<String, PowerPoint> hashMap = new HashMap<>();

    public PowerPoint getPPT(Class<? extends PowerPoint> clazz) {
        try {
            String name = clazz.getName();
            if (hashMap.keySet().contains(name)) {
                returnhashMap.get(name); } Constructor<? > constructor = Class.forName(name).getConstructor(String.class); PowerPoint powerPoint = (PowerPoint) constructor.newInstance("PPT factory version all");
            hashMap.put(name, powerPoint);
            return powerPoint;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null; }}Copy the code

Call the factory class to create or get a meta-object:

public class ZTest {
    public static void main(String[] args) {
        PPTFactory pptFactory = new PPTFactory();
        PowerPoint ppt01 = pptFactory.getPPT(BusinessPPT.class);
        ppt01.setTitle("First Quarter Work Report");
        System.out.println(ppt01);
        PowerPoint ppt02 = pptFactory.getPPT(BusinessPPT.class);
        ppt02.setTitle("Second Quarter Performance Report");
        System.out.println(ppt02);
        PowerPoint ppt03 = pptFactory.getPPT(SciencePPT.class);
        ppt03.setTitle("Science Exhibition Report"); System.out.println(ppt03); }}/ / output:No. :1744347043: PowerPoint{copyright='PPT Factory Version all ', title='First Quarter Work Report'} number:1744347043: PowerPoint{copyright='PPT Factory Version all ', title='Second Quarter Performance Report'} number:662441761: PowerPoint{copyright='PPT Factory Version all ', title='Technology Expo Report'}
Copy the code

Behavior type

1. Observer mode

1.1 define

Define a one-to-many dependency between objects so that when an object’s state changes, all dependent objects are notified and automatically updated.

1.2 the advantages

Reduces the coupling between the target object and the observer.

1.3 the sample

Assuming that multiple users follow a particular merchant, when a merchant sends a notification such as a price reduction, all users should receive:

Observed interface and merchant implementation classes:

public interface Observable {

    // Receive the observer
    void addObserver(Observer observer);

    // Remove the observer
    void removeObserver(Observer observer);
    
    // Notify the observer
    void notifyObservers(String message);
}
Copy the code
public class Business implements Observable {

    private List<Observer> observerList = new ArrayList<>();

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

    @Override
    public void removeObserver(Observer observer) {
        observerList.remove(observer);
    }

    @Override
    public void notifyObservers(String message) {
        for(Observer observer : observerList) { observer.receive(message); }}}Copy the code

Observer interface and user implementation classes:

public interface Observer {
    void receive(String message);
}
Copy the code
public class User implements Observer {

    private String name;

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

    @Override
    public void receive(String message) {
        System.out.println(getName() + "Received a message:"+ message); }}Copy the code

Test merchant sending message:

Business business = new Business();
business.addObserver(new User("User 1"));
business.addObserver(new User("User 2"));
business.addObserver(new User("User 3"));
business.notifyObservers("Product Promotion Notice");

/ / output:The user1Received message: Product promotion notification user2Received message: Product promotion notification user3Received the message: merchandise promotion noticeCopy the code

2. Chain of responsibility mode

2.1 define

This avoids coupling between the sender and receiver of the request by giving multiple objects the opportunity to process the request. Connect these objects into a chain, and pass the request along the chain until one of the objects finishes processing it.

2.2 the advantages

  • Reduce the degree of coupling between objects;
  • The scalability of the system is enhanced. New processing classes can be added according to demand to meet the open and close principle;
  • Enhanced system flexibility. When the workflow changes, you can dynamically add, delete members or modify their transfer order;
  • Each object needs to keep only one reference to its successors, rather than all the other handlers, thus avoiding excessive conditional statements.
  • Each class handles only what it needs to handle, and passes on what it can’t handle to the next object, in line with the single responsibility principle of a class.

2.3 the sample

Suppose a normal process, according to the different days of leave, need to be jointly approved by different leaders:

Application form:

public class Application {
    private String title;
    /* Days of leave */
    private int dayNum;
}
Copy the code

Abstract leadership classes:

public abstract class Leader {

    protected Leader leader;

    // The core of the chain of responsibility model: it needs to have a successor
    public Leader setNextLeader(Leader leader) {
        this.leader = leader;
        return leader;
    }

    public abstract void approval(Application application);

}
Copy the code

Leave of less than 3 days only needs to be reviewed by the team leader:

public class GroupLeader extends Leader {
    @Override
    public void approval(Application application) {
        System.out.println(application.getTitle() + "Approved by the group leader");
        if (application.getDayNum() >= 3) { leader.approval(application); }}}Copy the code

Leave of more than 3 days and less than 5 days shall be reviewed by the team leader and the department manager together:

public class DepartManager extends Leader {
    @Override
    public void approval(Application application) {
        System.out.println(application.getTitle() + "Approved by department manager");
        if (application.getDayNum() >= 5) { leader.approval(application); }}}Copy the code

Leave of more than 5 days shall be reviewed by the general manager:

public class President extends Leader {
    @Override
    public void approval(Application application) {
        System.out.println(application.getTitle() + "Approved by the general manager"); }}Copy the code

Establish responsibility chain and test:

GroupLeader groupLeader = new GroupLeader();
DepartManager departManager = new DepartManager();
President president = new President();
groupLeader.setNextLeader(departManager).setNextLeader(president);
groupLeader.approval(new Application("Personal Leave Form".3));
groupLeader.approval(new Application("Wedding leave Slip".10));

/ / output:The personal leave document was approved by the group leader and approved by the department manager and approved by the general managerCopy the code

3. Template method pattern

3.1 define

Defining the algorithm skeleton of an operation and deferring some steps of the algorithm to a subclass allows the subclass to redefine specific steps of the algorithm without changing the algorithm structure. It usually contains the following roles:

Abstract Class: Responsible for giving the outline and skeleton of an algorithm. It consists of a template method and several basic methods:

  • Template methods: Define the skeleton of an algorithm that calls its contained base methods in some order.
  • Basic methods: Can be concrete methods, abstract methods, or hook methods (implemented in abstract classes, including logical methods for judgment and empty methods that need to be overridden by subclasses).

Concrete Class: Implements abstract methods and hook methods defined in abstract classes.

3.2 the advantages

  • The parent class defines common behavior that enables code reuse.
  • Subclasses can be extended to add functionality in accordance with the open closed principle.

3.3 the sample

Mobile phones generally have modules such as batteries and cameras, but not all mobile phones have NFC modules. If template mode is used to build, the relevant codes are as follows:

Abstract parent class:

public abstract class Phone {

    // Template method
    public void assembling(a) {
        adCamera();
        addBattery();
        if (needAddNFC()) {
            addNFC();
        }
        packaged();
    }

    // Specific methods
    private void adCamera(a) {
        System.out.println("Assemble the camera.");
    }

    private void addBattery(a) {
        System.out.println("Install the battery");
    }

    private void addNFC(a) {
        System.out.println("Add NFC functionality");
    }

    // Hook method
    abstract boolean needAddNFC(a);

    // Abstract methods
    abstract void packaged(a);
}
Copy the code

Specific subclasses:

public class OlderPhone extends Phone {

    @Override
    boolean needAddNFC(a) {
        return false;
    }

    @Override
    void packaged(a) {
        System.out.println("It comes with a phone case."); }}Copy the code
public class SmartPhone extends Phone {

    @Override
    boolean needAddNFC(a) {
        return true;
    }

    @Override
    void packaged(a) {
        System.out.println("It comes with a pair of headphones."); }}Copy the code

The test and output results are as follows:

OlderPhone olderPhone = new OlderPhone();
olderPhone.assembling();
/ / output:Assemble the camera to install the battery and come with a phone casenew SmartPhone();
smartPhone.assembling();
/ / output:Assemble camera, install battery, add NFC function and get a pair of earphonesCopy the code

4. Policy mode

4.1 define

Define a series of algorithms and encapsulate them independently before providing them to the client for use, so that the change of the algorithm does not affect the use of the client. The policy pattern is actually a ubiquitous pattern, such as calling different services for processing depending on the parameters received at the Controller layer, which is also reflected in the policy pattern.

4.2 the sample

Suppose the company needs to choose different employee incentive strategies according to different turnover:

Policy interfaces and their implementation classes:

public interface Strategy {
    void execute(a);
}

public class TravelStrategy implements Strategy {
    @Override
    public void execute(a) {
        System.out.println("Group travel"); }}public class BonusStrategy implements Strategy {
    @Override
    public void execute(a) {
        System.out.println("Bonus incentive"); }}public class WorkOvertimeStrategy implements Strategy {
    @Override
    public void execute(a) {
        System.out.println("Reward overtime."); }}Copy the code

Company:

public class Company {

    private Strategy strategy;

    public Company setStrategy(Strategy strategy) {
        this.strategy = strategy;
        return this;
    }

    public void execute(a) { strategy.execute(); }}Copy the code

When using the client, different incentive strategies should be selected according to different turnover:

public static void main(String[] args) {
    / / turnover
    int turnover = Integer.parseInt(args[0]);
    Company company = new Company();
    if (turnover > 1000) {
        company.setStrategy(new BonusStrategy()).execute();
    } else if (turnover > 100) {
        company.setStrategy(new TravelStrategy()).execute();
    } else {
        company.setStrategy(newWorkOvertimeStrategy()).execute(); }}Copy the code

6. State mode

6.1 define

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

6.2 the advantages

  • State mode localizes the behaviors related to specific states into one state, and separates the behaviors of different states to satisfy the single responsibility principle.
  • Introducing different states into independent objects makes state transitions more explicit and reduces the interdependence between objects.
  • It makes it easy to add new states and transitions by defining new subclasses.

6.3 the sample

Suppose we are developing a player that has four basic states as shown below: Play, Close, pause, and Accelerate. These four states can be converted to each other, but there are certain limitations, for example, the video cannot be accelerated in the closed or paused state. The relevant codes of the player in the state mode are as follows:

Define the state abstract class:

public class State {

    private Player player;

    public void setPlayer(Player player) {
        this.player = player;
    }

    public void paly(a) {
        player.setState(Player.PLAY_STATE);
    }

    public void pause(a) {
        player.setState(Player.PAUSE_STATE);
    }

    public void close(a) {
        player.setState(Player.CLOSE_STATE);
    }

    public void speed(a) { player.setState(Player.SPEED_STATE); }}Copy the code

Define concrete implementation classes for four states and limit the transformation relationships between them:

public class PlayState extends State {}Copy the code
public class CloseState extends State {
    @Override
    public void pause(a) {
        System.out.println("Operation failed: Video is closed, no need to pause.");
    }
    @Override
    public void speed(a) {
        System.out.println("Operation failed: Video is closed and cannot be accelerated"); }}Copy the code
public class PauseState extends State {
    @Override
    public void speed(a) {
        System.out.print("Operation failed: Acceleration is not supported in paused state"); }}Copy the code
public class SpeedState extends State {
    @Override
    public void paly(a) {
        System.out.println("System warning: You are currently in accelerated playback state"); }}Copy the code

Assembly player:

public class Player {

    private State state;

    public final static PlayState PLAY_STATE = new PlayState();
    public final static PauseState PAUSE_STATE = new PauseState();
    public final static CloseState CLOSE_STATE = new CloseState();
    public final static SpeedState SPEED_STATE = new SpeedState();

    public State getState(a) {
        return state;
    }

    public void setState(State state) {
        this.state = state;
        this.state.setPlayer(this);
    }

    Player() {
        // Assume the initial state of the player is closed
        this.state = new CloseState();
        this.state.setPlayer(this);
    }

    public void paly(a) {
        System.out.println("Play video");
        state.paly();
    }

    public void pause(a) {
        System.out.println("Pause video");
        state.pause();
    }

    public void close(a) {
        System.out.println("Close the video");
        state.close();
    }

    public void speed(a) {
        System.out.println("Video acceleration"); state.speed(); }}Copy the code

Call our custom player:

Player player = new Player();
player.speed();
player.paly();
player.speed();
player.paly();
player.pause();
player.close();
player.speed();

/ / output:Video acceleration failed: The video is shut down and cannot be accelerated. Video acceleration Play The video system prompts: You are in acceleration play. Pause the videoCopy the code

7. The Mediator model

7.1 define

Define a mediation object that encapsulates the interactions between a set of objects, loosens the coupling between the original objects, and can change their interactions independently.

7.2 the advantages

Reduces the degree of coupling between objects.

7.3 the sample

Taking a real estate agent as an example, we define an abstract intermediary class that is responsible for receiving customers and passing messages between them:

abstract class Mediator {

    public abstract void register(Person person);

    public abstract void send(String from, String message);
}
Copy the code

A specific real estate agent who broadcasts the seller’s sale to all:

public class HouseMediator extends Mediator {

    private List<Person> personList = new ArrayList<>();

    @Override
    public void register(Person person) {
        if(! personList.contains(person)) { personList.add(person); person.setMediator(this); }}@Override
    public void send(String from, String message) {
        System.out.println(from + "Send a message:" + message);
        for (Person person : personList) {
            String name = person.getName();
            if(! name.equals(from)) { person.receive(message); }}}}Copy the code

Define a user class that can be either a buyer or a seller, both of which are clients of the mediation:

public class Person {

    private String name;
    private Mediator mediator;

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

    public void send(String message) {
        mediator.send(this.name, message);
    }

    public void receive(String message) {
        System.out.println(name + "Received a message:"+ message); }}Copy the code

Finally, buyers and sellers can communicate with each other through intermediaries:

public class ZTest {
    public static void main(String[] args) {
        HouseMediator houseMediator = new HouseMediator();
        Person seller = new Person("The seller");
        Person buyer = new Person("The buyer");
        houseMediator.register(seller);
        houseMediator.register(buyer);
        buyer.send("What's the price?");
        seller.send("100000");
        buyer.send("It's too expensive"); }}/ / output:The buyer sends a message: what price does the seller receive a message: What price does the seller send a message:10Buyer receives message:10The buyer sends a message: Too expensive the seller receives a message: too expensiveCopy the code

8. Iterator pattern

8.1 define

Provides a way to access the elements of an aggregate object sequentially without exposing the internal representation of the object.

8.2 the sample

Suppose we now iterate through all the books on the shelf, first defining the book class:

public class Book {
    private String name;
    public Book(String name) {
        this.name = name; }}Copy the code

Define the bookcase interface and its implementation class:

public interface Bookshelf {
    void addBook(Book book);
    void removeBook(Book book);
    BookIterator iterator(a);
}

public class BookshelfImpl implements Bookshelf {

    private List<Book> bookList = new ArrayList<>();

    @Override
    public void addBook(Book book) {
        bookList.add(book);
    }

    @Override
    public void removeBook(Book book) {
        bookList.remove(book);
    }

    @Override
    public BookIterator iterator(a) {
        return newBookIterator(bookList); }}Copy the code

Define the iterator interface and its implementation class:

public interface Iterator<E> {
    E next(a);
    boolean hasNext(a);
}

public class BookIterator implements Iterator<Book> {

    private List<Book> bookList;
    private int position = 0;

    public BookIterator(List<Book> bookList) {
        this.bookList = bookList;
    }

    @Override
    public Book next(a) {
        return bookList.get(position++);
    }

    @Override
    public boolean hasNext(a) {
        returnposition < bookList.size(); }}Copy the code

Call a custom iterator for traversal:

BookshelfImpl bookshelf = new BookshelfImpl();
bookshelf.addBook(new Book("Java books"));
bookshelf.addBook(new Book("Python books"));
bookshelf.addBook(new Book("Books on the Go"));
BookIterator iterator = bookshelf.iterator();
while (iterator.hasNext()) {
    System.out.println(iterator.next());
}
Copy the code

9. Visitor pattern

9.1 define

Represents an operation on elements in an object structure that allows the user to define operations on those elements without changing their corresponding classes.

9.2 the advantages and disadvantages

Advantages:

  • Extensibility: The ability to add new functionality to elements in an object structure without modifying them.
  • Good reuse: Visitors can define common functions throughout the object structure, thus improving the degree of reuse of the system.
  • Good flexibility: The visitor pattern decouples data structures from operations acting on them, allowing operations to evolve relatively freely without affecting the system’s data structure.

Disadvantages:

  • Adding new element classes is complicated;
  • The implementation is relatively cumbersome.

9.3 the sample

Generally, employees at different levels have different access rights to company files. For easy understanding, as shown in the figure below, it is assumed that only public and encrypted files are available, and only general manager and department manager can enter the archives room:

Define archive classes and their implementation classes:

public interface Archive {
    // Accept visitors
    void accept(Visitor visitor);
}

public class PublicArchive implements Archive {
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this); }}public class SecretArchive implements Archive {
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this); }}Copy the code

Define the visitor interface and its implementation classes:

public interface Visitor {
    // Access public files
    void visit(PublicArchive publicArchive);
    // Access encrypted files
    void visit(SecretArchive secretArchive);
}
Copy the code
public class DepartManager implements Visitor {

    @Override
    public void visit(PublicArchive publicArchive) {
        System.out.println("All open records.");
    }

    @Override
    public void visit(SecretArchive secretArchive) {
        System.out.println("Encrypted files with permissions below level 3"); }}Copy the code
public class President implements Visitor {

    @Override
    public void visit(PublicArchive publicArchive) {
        System.out.println("All open records.");
    }

    @Override
    public void visit(SecretArchive secretArchive) {
        System.out.println("All encrypted files"); }}Copy the code

Manage access through the company class:

public class Company {

    private List<Archive> archives = new ArrayList<>();

    // Accept files
    void add(Archive archive) {
        archives.add(archive);
    }

    // Remove the file
    void remove(Archive archive) {
        archives.remove(archive);
    }

    // Receive visitors
    void accept(Visitor visitor) {
        for(Archive archive : archives) { archive.accept(visitor); }}}Copy the code

Test and output results:

Company company = new Company();
company.add(new SecretArchive());
company.add(new PublicArchive());

company.accept(new DepartManager());
/ / output:All public files company.accept(new President());
/ / output:All encrypted files all public filesCopy the code

10. Memo mode

10.1 define

Without breaking encapsulation, capture the internal state of an object and store the state outside of the object so that the object can be restored to its original state if needed.

10.2 the advantages and disadvantages

The advantage is that it can be used for recovery under abnormal circumstances, and can improve the security of the system. The downside is that additional storage is required to store the historical state.

10.3 the sample

The undo function of the editor and the snapshot function of the database are typical implementations of the memo mode. Here we take Git to save historical version information as an example to demonstrate:

The article types:

public class Article {
    private String title;
    private String content;
}
Copy the code

Depending on your business needs, you may only need to save some fields of the data, or you may need to add additional fields (such as save time, etc.), so you need to convert the target object to a memo object when saving:

// The memo object
public class Memorandum {

    private String title;
    private String content;
    private Date createTime;

    // Create a memo object based on the target object
    public Memorandum(Article article) {
        this.title = article.getTitle();
        this.content = article.getContent();
        this.createTime = new Date();
    }

    public Article toArticle(a) {
        return new Article(this.title, this.content); }}Copy the code

Managers:

public class GitRepository {

    private List<Memorandum> repository = new ArrayList<>();

    // Save the current data
    public void save(Article article) {
        Memorandum memorandum = new Memorandum(article);
        repository.add(memorandum);
    }

    // Get the data for the specified version class
    public Article get(int version) {
        Memorandum memorandum = repository.get(version);
        return memorandum.toArticle();
    }

    // Undo the current operation
    public Article back(a) {
        return repository.get(repository.size() - 1).toArticle(); }}Copy the code

The test class:

GitRepository repository = new GitRepository();
Article article = new Article("Java manual"."Version one");
repository.save(article);
article.setContent("Version 2");
repository.save(article);
article.setContent("Version 3");
repository.save(article);
System.out.println(repository.back());
System.out.println(repository.get(0));
Copy the code

11. Interpreter mode

Define a language for the analysis object and define its grammatical representation, then design a parser to interpret the statements in the language and analyze the instances in the application in the way of compiling the language. The interpreter pattern is complex to implement and rarely used in normal development, so no examples are provided here.

See design-Pattern for the source code for all of the above examples

The resources

  1. Erich Gamma / Richard Helm / Ralph Johnson / John Vlissides . Design Patterns (The Foundation of Reusable Object-oriented Software). China Machine Press. 2000-9
  2. Qin Xiaobo. Zen of Design Pattern (2nd edition). China Machine Press. 2014-2-25
  3. Java Design Pattern
  4. Java Design mode Debug mode and memory analysis

For more articles, please visit the full stack Engineer manual at GitHub.Github.com/heibaiying/…