Detailed explanation of 23 design modes (for review)

Design Patterns

                              -The basis of Reusable Object-Oriented Software

Recently, I saw a big man on the blog who said that the design mode was very good. So I moved the big man's blog and sorted it out. I'll review it later. First of all, I'd like to thank the big man. The original address is: Design pattern of the beauty of Java [from rookie to expert]

1, Classification of design patterns

Generally speaking, design patterns are divided into three categories:
There are five kinds of creation mode: factory method mode, abstract factory mode, singleton mode, builder mode and prototype mode.
There are seven structural modes: adapter mode, decorator mode, agent mode, appearance mode, bridge mode, combination mode and sharing mode.
There are eleven behavioral modes: strategy mode, template method mode, observer mode, iteration sub mode, responsibility chain mode, command mode, memo mode, state mode, visitor mode, mediator mode and interpreter mode.
In fact, there are two types: merge mode and thread pool mode. Describe it as a whole with a picture:

2, Six principles of design pattern

1. Open Close Principle
The opening and closing principle means that it is open to extensions and closed to modifications. When the program needs to be expanded, you can't modify the original code to achieve a hot plug effect. So in a word: in order to make the program extensible, easy to maintain and upgrade. To achieve this effect, we need to use interfaces and abstract classes, which will be mentioned in the following specific design.
2. Liskov Substitution Principle
Liskov Substitution Principle (LSP) is one of the basic principles of object-oriented design. The Richter substitution principle says that wherever a base class can appear, a subclass must appear. LSP is the cornerstone of inheritance reuse. Only when the derived class can replace the base class and the function of the software unit is not affected, the base class can be truly reused, and the derived class can also add new behavior on the basis of the base class. Richter's substitution principle is a supplement to the "open close" principle. The key step in realizing the "open close" principle is abstraction. The inheritance relationship between base class and subclass is the concrete realization of abstraction, so the Richter substitution principle is the specification of the specific steps of realizing abstraction—— From Baidu Encyclopedia
3. Dependency Inversion Principle
This is the basis of the opening and closing principle. The specific content: true interface programming depends on abstraction rather than concrete.
4. Interface Segregation Principle
This principle means that using multiple isolated interfaces is better than using a single interface. It also means to reduce the coupling between classes. From here, we can see that in fact, the design pattern is a software design idea, starting from the large-scale software architecture for the convenience of upgrading and maintenance. Therefore, it appears many times above: reduce dependence and coupling.
5. Demeter Principle
Why is it called the least known principle, that is, an entity should interact with other entities as little as possible to make the system functional modules relatively independent.
6. Composite Reuse Principle
The principle is to try to use composition / aggregation instead of inheritance.

3, Design patterns in Java

Starting from this part, we introduce the concepts and application scenarios of 23 design patterns in Java in detail, and analyze them in combination with their characteristics and the principles of design patterns.

1. Factory Method

There are three factory method modes:
11. Common factory mode is to create a factory class and create instances of some classes that implement the same interface. First look at the relationship diagram:

Examples are as follows: (let's give an example of sending email and SMS)
First, create a common interface between the two:

public interface Sender {  
    public void Send();  
}  

Secondly, create an implementation class:

public class MailSender implements Sender {  
    @Override  
    public void Send() {  
        System.out.println("this is mailsender!");  
    }  
}  
public class SmsSender implements Sender {  
  
    @Override  
    public void Send() {  
        System.out.println("this is sms sender!");  
    }  
}  

Finally, plant construction:

public class SendFactory {  
  
    public Sender produce(String type) {  
        if ("mail".equals(type)) {  
            return new MailSender();  
        } else if ("sms".equals(type)) {  
            return new SmsSender();  
        } else {  
            System.out.println("Please enter the correct type!");  
            return null;  
        }  
    }  
}  

Let's test:

public class FactoryTest {  
  
    public static void main(String[] args) {  
        SendFactory factory = new SendFactory();  
        Sender sender = factory.produce("sms");  
        sender.Send();  
    }  
}  
Output: this is sms sender!

22. Multiple factory method mode is an improvement on the ordinary factory method mode. In the ordinary factory method mode, if the string passed is wrong, the object cannot be created correctly, while multiple factory method mode provides multiple factory methods to create objects respectively. Diagram:

Modify the above code and change the SendFactory class as follows:

public class SendFactory {  
   public Sender produceMail(){  
        return new MailSender();  
    }  
      
    public Sender produceSms(){  
        return new SmsSender();  
    }  
}  

The test classes are as follows:

public class FactoryTest {    
    public static void main(String[] args) {  
        SendFactory factory = new SendFactory();  
        Sender sender = factory.produceMail();  
        sender.Send();  
    }  
}  
Output: this is mailsender!

33. Static factory method mode: set the methods in the above factory method modes as static, and you can call them directly without creating an instance.

public class SendFactory {  
      
    public static Sender produceMail(){  
        return new MailSender();  
    }  
      
    public static Sender produceSms(){  
        return new SmsSender();  
    }  
}  
public class FactoryTest {  
  
    public static void main(String[] args) {      
        Sender sender = SendFactory.produceMail();  
        sender.Send();  
    }  
}  
Output: this is mailsender!

Generally speaking, the factory mode is suitable: when a large number of products need to be created and have common interfaces, they can be created through the factory method mode. In the above three modes, the first one can't create objects correctly if the incoming string is wrong. Compared with the second one, the third one doesn't need an instance factory class. Therefore, in most cases, we will choose the third one - static factory method mode.

2. Abstract Factory pattern

A problem with the factory method pattern is that the creation of classes depends on factory classes, that is, if you want to expand the program, you must modify the factory class, which violates the closure principle. Therefore, from the perspective of design, there are certain problems. How to solve them? The abstract factory pattern is used to create multiple factory classes. In this way, once new functions need to be added, new factory classes can be added directly without modifying the previous code. Because the abstract factory is not easy to understand, let's look at the diagram first, and then compare it with the code, which is easier to understand.

Take an example:

public interface Sender {  
    public void Send();  
} 

Two implementation classes:

public class MailSender implements Sender {  
    @Override  
    public void Send() {  
        System.out.println("this is mailsender!");  
    }  
}  
public class SmsSender implements Sender {  
 
    @Override  
    public void Send() {  
        System.out.println("this is sms sender!");  
    }  
}

Two factory classes:

public class SendMailFactory implements Provider {  
      
    @Override  
    public Sender produce(){  
        return new MailSender();  
    }  
} 
public class SendSmsFactory implements Provider{  
  
    @Override  
    public Sender produce() {  
        return new SmsSender();  
    }  
}  

When providing an interface:

public interface Provider {  
    public Sender produce();  
}  

Test class:

public class Test {  
  
    public static void main(String[] args) {  
        Provider provider = new SendMailFactory();  
        Sender sender = provider.produce();  
        sender.Send();  
    }  
} 

In fact, the advantage of this mode is that if you want to add a function: sending timely information, you only need to make an implementation class to implement the Sender interface and a factory class to implement the Provider interface. It's OK without changing the ready-made code. Better expansibility!

3. Singleton mode

Singleton is a common design pattern. In Java applications, a singleton object can ensure that only one instance of the object exists in a JVM. This model has several advantages:
1. Some classes are created frequently, which is a great overhead for some large objects.
2. The new operator is omitted, which reduces the use frequency of system memory and reduces the pressure of GC.
3. Some classes, such as the core trading engine of the exchange, control the trading process. If more than one class can be created, the system will be completely chaotic. (for example, if multiple commanders command at the same time in an army, it will be in a mess). Therefore, only using the single case mode can ensure that the core transaction server can independently control the whole process.
First, let's write a simple singleton class:

public class Singleton {  
  
    /* Hold a private static instance to prevent it from being referenced. The value here is null to achieve delayed loading */  
    private static Singleton instance = null;  
  
    /* Private construction method to prevent instantiation */  
    private Singleton() {  
    }  
  
    /* Static engineering method, creating instances */  
    public static Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
    }  
  
    /* If the object is used for serialization, it can ensure that the object remains consistent before and after serialization */  
    public Object readResolve() {  
        return instance;  
    }  
}  

This class can meet the basic requirements. However, if we put such a class without thread safety protection into a multithreaded environment, there will be problems. How to solve them? We will first think of adding the synchronized keyword to the getInstance method, as follows:

public static synchronized Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
    }  

However, the synchronized keyword locks the object. This usage will degrade the performance, because the object must be locked every time getInstance() is called. In fact, the lock is only needed when the object is created for the first time, and it is not needed after that. Therefore, this place needs to be improved. Let's change to the following:

public static Singleton getInstance() {  
        if (instance == null) {  
            synchronized (instance) {  
                if (instance == null) {  
                    instance = new Singleton();  
                }  
            }  
        }  
        return instance;  
    }  

It seems to solve the problem mentioned earlier. The synchronized keyword is added internally, that is, it does not need to be locked when calling. It needs to be locked only when instance is null and an object is created. The performance has been improved to A certain extent. However, in this case, there may still be problems. Look at the following situation: the creation of objects and assignment operations in Java instructions are carried out separately, that is, instance = new Singleton(); The statement is executed in two steps. However, the JVM does not guarantee the sequence of these two operations, that is, it is possible that the JVM will allocate space for the new Singleton instance, and then directly assign it to the instance member, and then initialize the Singleton instance. In this way, errors may occur. Let's take threads A and B as examples:
a> Threads a and B enter the first if judgment at the same time
b> A first enters the synchronized block. Since instance is null, it executes instance = new Singleton();
c> Due to the internal optimization mechanism of the JVM, the JVM first draws some blank memory allocated to the Singleton instance and assigns it to the instance member (note that the JVM does not start initializing this instance at this time), and then A leaves the synchronized block.
d> B enters the synchronized block. Since instance is not null at this time, it immediately leaves the synchronized block and returns the result to the program calling the method.
e> At this time, thread B intends to use the Singleton instance, but finds that it has not been initialized, so an error occurs.
Therefore, the program may still have errors. In fact, the running process of the program is very complex. From this point, we can see that it is more difficult and challenging to write programs, especially in a multithreaded environment. We further optimized the program:

private static class SingletonFactory{           
        private static Singleton instance = new Singleton();           
    }           
    public static Singleton getInstance(){           
        return SingletonFactory.instance;           
    }   

In fact, the singleton mode uses internal classes to maintain the implementation of singletons. The internal mechanism of the JVM can ensure that when a class is loaded, the loading process of this class is mutually exclusive. In this way, when we call getInstance for the first time, the JVM can help us ensure that the instance is created only once and that the memory assigned to the instance is initialized, so we don't have to worry about the above problems. At the same time, this method will only use the mutual exclusion mechanism at the first call, which solves the low performance problem. In this way, we temporarily summarize a perfect singleton mode:

public class Singleton {  
  
    /* Private construction method to prevent instantiation */  
    private Singleton() {  
    }  
  
    /* An inner class is used here to maintain the singleton */  
    private static class SingletonFactory {  
        private static Singleton instance = new Singleton();  
    }  
  
    /* Get instance */  
    public static Singleton getInstance() {  
        return SingletonFactory.instance;  
    }  
  
    /* If the object is used for serialization, it can ensure that the object remains consistent before and after serialization */  
    public Object readResolve() {  
        return getInstance();  
    }  
}  

In fact, it is not necessarily perfect. If an exception is thrown in the constructor, the instance will never be created and errors will occur. Therefore, there is no perfect thing. We can only choose the implementation method most suitable for our own application scenario according to the actual situation. It is also implemented in this way: because we only need to synchronize when creating classes, it is also possible to separate creation from getInstance() and add synchronized keyword for creation separately:

public class SingletonTest {  
  
    private static SingletonTest instance = null;  
  
    private SingletonTest() {  
    }  
  
    private static synchronized void syncInit() {  
        if (instance == null) {  
            instance = new SingletonTest();  
        }  
    }  
  
    public static SingletonTest getInstance() {  
        if (instance == null) {  
            syncInit();  
        }  
        return instance;  
    }  
}  

Considering the performance, the whole program only needs to create an instance once, so the performance will not be affected.
Supplement: the "shadow instance" method is used to update the attributes of the single instance object synchronously

public class SingletonTest {  
  
    private static SingletonTest instance = null;  
    private Vector properties = null;  
  
    public Vector getProperties() {  
        return properties;  
    }  
  
    private SingletonTest() {  
    }  
  
    private static synchronized void syncInit() {  
        if (instance == null) {  
            instance = new SingletonTest();  
        }  
    }  
  
    public static SingletonTest getInstance() {  
        if (instance == null) {  
            syncInit();  
        }  
        return instance;  
    }  
  
    public void updateProperties() {  
        SingletonTest shadow = new SingletonTest();  
        properties = shadow.getProperties();  
    }  
}  

Through the learning of singleton mode, we can know that:
1. The singleton pattern is simple to understand, but it is still difficult to implement.
2. The synchronized keyword locks objects. When you use it, you must use it in the right place (pay attention to the objects and processes that need locks. Sometimes not the whole object and process need locks).
Here, the singleton mode has been basically finished. At the end, the author suddenly thought of another question, that is, it is feasible to use the static method of class to realize the effect of singleton mode. What is the difference between the two here?
First, static classes cannot implement interfaces. (it's OK from the perspective of class, but that will destroy the static. Because static modified methods are not allowed in the interface, even if they are implemented, they are non-static)
Secondly, a singleton can be initialized late. Static classes are usually initialized when they are loaded for the first time. The reason for delayed loading is that some classes are relatively large, so delayed loading helps to improve performance.
Thirdly, the singleton class can be inherited and its methods can be overridden. However, the internal methods of static classes are static and cannot be overridden.
Finally, the singleton class is more flexible. After all, it is only an ordinary Java class in terms of implementation. As long as you meet the basic needs of singleton, you can implement some other functions at will, but static classes can't. From the above generalizations, we can basically see the difference between the two. However, on the other hand, the singleton pattern we finally implemented above is internally implemented by a static class. Therefore, the two are closely related, but we consider different levels of the problem. The combination of the two ideas can create a perfect solution, just as HashMap uses array + linked list to achieve. In fact, many things in life are like this. Using different methods to deal with problems alone always has advantages and disadvantages. The most perfect way is to combine the advantages of various methods to solve problems best!

4. Builder mode (builder)

The factory class pattern provides the pattern of creating a single class, while the builder pattern centralizes various products for management and is used to create composite objects. The so-called composite object means that a class has different properties. In fact, the builder pattern is obtained by combining the previous abstract factory pattern with the final Test. Let's look at the code:
As before, there is a Sender interface and two implementation classes MailSender and SmsSender. Finally, the builder class is as follows:

public class Builder {  
      
    private List<Sender> list = new ArrayList<Sender>();  
      
    public void produceMailSender(int count){  
        for(int i=0; i<count; i++){  
            list.add(new MailSender());  
        }  
    }  
      
    public void produceSmsSender(int count){  
        for(int i=0; i<count; i++){  
            list.add(new SmsSender());  
        }  
    }  
}  

Test class:

public class Test {  
  
    public static void main(String[] args) {  
        Builder builder = new Builder();  
        builder.produceMailSender(10);  
    }  
}  

From this point of view, the builder pattern integrates many functions into a class, which can create more complex things. Therefore, the difference from the engineering mode is that the factory mode focuses on creating a single product, while the builder mode focuses on creating conforming objects and multiple parts. Therefore, the choice of factory mode or builder mode depends on the actual situation.

5. Prototype

Although the prototype mode is a creation mode, it has nothing to do with the engineering mode. From the name, we can see that the idea of this mode is to take an object as a prototype, copy and clone it, and produce a new object similar to the original object. This summary will be explained by copying objects. In Java, the copy object is implemented through clone(). First create a prototype class:

public class Prototype implements Cloneable {  
  
    public Object clone() throws CloneNotSupportedException {  
        Prototype proto = (Prototype) super.clone();  
        return proto;  
    }  
}  

Very simply, a prototype class only needs to implement the clonable interface and override the clone method. Here, the clone method can be changed to any name. Because the clonable interface is an empty interface, you can define the method name of the implementation class arbitrarily, such as cloneA or cloneB, because the focus here is super clone(), super Clone () calls the clone () method of Object, but in the Object class, clone () is native. How to implement it? I will explain the call of local methods in Java in another article, which will not be discussed here. Here, I will combine the shallow copy and deep copy of objects. First, we need to understand the concepts of deep and shallow copy of objects:
Shallow copy: after copying an object, the variables of the basic data type will be recreated, and the reference type still points to the original object.
Deep copy: after copying an object, both basic data types and reference types are recreated. In short, deep replication is a complete and complete replication, while shallow replication is not complete.
Here, write an example of deep and shallow replication:

public class Prototype implements Cloneable, Serializable {  
  
    private static final long serialVersionUID = 1L;  
    private String string;  
  
    private SerializableObject obj;  
  
    /* Shallow replication */  
    public Object clone() throws CloneNotSupportedException {  
        Prototype proto = (Prototype) super.clone();  
        return proto;  
    }  
  
    /* Deep replication */  
    public Object deepClone() throws IOException, ClassNotFoundException {  
  
        /* Writes the binary stream of the current object */  
        ByteArrayOutputStream bos = new ByteArrayOutputStream();  
        ObjectOutputStream oos = new ObjectOutputStream(bos);  
        oos.writeObject(this);  
  
        /* Read out the new object generated by the binary stream */  
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());  
        ObjectInputStream ois = new ObjectInputStream(bis);  
        return ois.readObject();  
    }  
  
    public String getString() {  
        return string;  
    }  
  
    public void setString(String string) {  
        this.string = string;  
    }  
  
    public SerializableObject getObj() {  
        return obj;  
    }  
  
    public void setObj(SerializableObject obj) {  
        this.obj = obj;  
    }  
  
}  
  
class SerializableObject implements Serializable {  
    private static final long serialVersionUID = 1L;  
}  

To realize deep replication, you need to read in the binary input of the current object in the form of stream, and then write the object corresponding to the binary data.

We then discuss design patterns. In the last article, I talked about five creation patterns. At the beginning of this chapter, I will talk about seven structural patterns: adapter pattern, decoration pattern, agent pattern, appearance pattern, bridge pattern, combination pattern and sharing pattern. The adapter mode of the object is the origin of various modes. Let's see the following figure:

The adapter pattern converts the interface of a class into another interface representation expected by the client. The purpose is to eliminate the compatibility problems of classes caused by interface mismatch. It is mainly divided into three categories: class adapter mode, object adapter mode and interface adapter mode. First, let's look at the adapter pattern of the class. First, let's look at the class diagram:

The core idea is: there is a Source class with a method to be adapted. When the target interface is Targetable, the function of Source is extended to Targetable through the Adapter class. See the code:

public class Source {  
  
    public void method1() {  
        System.out.println("this is original method!");  
    }  
}  
public interface Targetable {  
  
    /* The same method as in the original class */  
    public void method1();  
  
    /* Methods of new classes */  
    public void method2();  
}  
public class Adapter extends Source implements Targetable {  
  
    @Override  
    public void method2() {  
        System.out.println("this is the targetable method!");  
    }  
} 

The Adapter class inherits the Source class and implements the Targetable interface. The following is the test class:

public class AdapterTest {  
  
    public static void main(String[] args) {  
        Targetable target = new Adapter();  
        target.method1();  
        target.method2();  
    }  
}

Output:

this is original method!
this is the targetable method!

In this way, the implementation class of Targetable interface has the function of Source class.
Adapter mode for object
The basic idea is the same as the Adapter mode of the class, except that the Adapter class is modified. This time, it does not inherit the Source class, but holds an instance of the Source class to solve the problem of compatibility. Look at the picture:

Just modify the source code of the Adapter class:

public class Wrapper implements Targetable {  
  
    private Source source;  
      
    public Wrapper(Source source){  
        super();  
        this.source = source;  
    }  
    @Override  
    public void method2() {  
        System.out.println("this is the targetable method!");  
    }  
  
    @Override  
    public void method1() {  
        source.method1();  
    }  
}  

Test class:

public class AdapterTest {  
  
    public static void main(String[] args) {  
        Source source = new Source();  
        Targetable target = new Wrapper(source);  
        target.method1();  
        target.method2();  
    }  
}  

The output is the same as the first one, but the adaptation method is different.
The third adapter mode is the adapter mode of the interface. The adapter of the interface is like this: sometimes there are multiple abstract methods in an interface we write. When we write the implementation class of the interface, we must implement all the methods of the interface, which is obviously wasteful sometimes. Because not all the methods are needed, sometimes only some are needed. Here to solve this problem, We have introduced the adapter mode of the interface. With the help of an abstract class, the abstract class implements the interface and implements all methods. However, we do not deal with the original interface and only contact the abstract class. Therefore, we write a class, inherit the abstract class and rewrite the methods we need. Take a look at the class diagram:

Sometimes we need to understand too many methods in the development of this class, which is not very good. Look at the code:

public interface Sourceable {  
      
    public void method1();  
    public void method2();  
} 

Abstract class Wrapper2:

public abstract class Wrapper2 implements Sourceable{  
      
    public void method1(){}  
    public void method2(){}  
}  
public class SourceSub1 extends Wrapper2 {  
    public void method1(){  
        System.out.println("the sourceable interface's first Sub1!");  
    }  
}  
public class SourceSub2 extends Wrapper2 {  
    public void method2(){  
        System.out.println("the sourceable interface's second Sub2!");  
    }  
}  
public class WrapperTest {  
  
    public static void main(String[] args) {  
        Sourceable source1 = new SourceSub1();  
        Sourceable source2 = new SourceSub2();  
          
        source1.method1();  
        source1.method2();  
        source2.method1();  
        source2.method2();  
    }  
}  

Test output:

the sourceable interface's first Sub1!
the sourceable interface's second Sub2!

Achieved our effect!
After all, let's summarize the application scenarios of three adapter modes:
Class adapter mode: when you want to convert a class into a class that meets another new interface, you can use the class adapter mode to create a new class, inherit the original class and implement the new interface.
Object adapter mode: when you want to convert an object into an object that meets another new interface, you can create a Wrapper class and hold an instance of the original class. In the Wrapper class method, you can call the instance method.
Adapter mode of interface: when you don't want to implement all the methods in an interface, you can create an abstract class Wrapper to implement all the methods. When we write other classes, we can inherit the abstract class.

7. Decorator

As the name suggests, decoration mode is to add some new functions to an object, and it is dynamic. It is required that the decoration object and the decorated object implement the same interface. The decoration object holds the instance of the decorated object. The relationship diagram is as follows:

The Source class is a decorated class, and the Decorator class is a decorated class. You can dynamically add some functions to the Source class. The code is as follows:

public interface Sourceable {  
    public void method();  
}  
public class Source implements Sourceable {  
  
    @Override  
    public void method() {  
        System.out.println("the original method!");  
    }  
}  
public class Decorator implements Sourceable {  
  
    private Sourceable source;  
      
    public Decorator(Sourceable source){  
        super();  
        this.source = source;  
    }  
    @Override  
    public void method() {  
        System.out.println("before decorator!");  
        source.method();  
        System.out.println("after decorator!");  
    }  
} 

Test class:

public class DecoratorTest {  
  
    public static void main(String[] args) {  
        Sourceable source = new Source();  
        Sourceable obj = new Decorator(source);  
        obj.method();  
    }  
}  

Output:
before decorator!
the original method!
after decorator!
Application scenario of decorator mode:
1. You need to extend the function of a class.
2. Dynamically add functions to an object, and it can also be revoked dynamically. (inheritance cannot do this. The inherited function is static and cannot be added or deleted dynamically.)
Disadvantages: too many similar objects are generated, which is not easy to troubleshoot!

8. Proxy mode

In fact, the name of each mode indicates the function of the mode. The agent mode is to create one more agent class to perform some operations for the original object. For example, when we rent a house, we go back to find an intermediary. Why? Because you don't have comprehensive information about houses in this area, I hope to find a more familiar person to help you. That's what the agent here means. Another example is that we sometimes have a lawsuit, and we need to hire a lawyer, because the lawyer has expertise in law and can operate for us and express our ideas. Let's first look at the diagram:

According to the above description, the agent mode is easy to understand. Let's look at the code below:

public interface Sourceable {  
    public void method();  
}  
public class Source implements Sourceable {  
  
    @Override  
    public void method() {  
        System.out.println("the original method!");  
    }  
}  
public class Proxy implements Sourceable {  
  
    private Source source;  
    public Proxy(){  
        super();  
        this.source = new Source();  
    }  
    @Override  
    public void method() {  
        before();  
        source.method();  
        atfer();  
    }  
    private void atfer() {  
        System.out.println("after proxy!");  
    }  
    private void before() {  
        System.out.println("before proxy!");  
    }  
}  

Test class:

public class ProxyTest {  
  
    public static void main(String[] args) {  
        Sourceable source = new Proxy();  
        source.method();  
    }  
  
} 

Output:
before proxy!
the original method!
after proxy!
Application scenario of agent mode:
If the existing method needs to be improved when used, there are two methods:
1. Modify the original method to adapt. This violates the principle of "opening to expansion and closing to modification".
2. Is to use a proxy class to call the original method and control the results. This method is called proxy mode.
Using the agent mode, the functions can be divided more clearly, which is helpful for later maintenance!

9. Appearance mode (Facade)

The appearance mode is to solve the dependency between classes and class homes. Like spring, the relationship between classes can be configured into the configuration file. The appearance mode is to put their relationship in a Facade class, reducing the coupling between classes. There is no interface involved in this mode. See the class diagram below: (let's take the startup process of a computer as an example)

Let's first look at the implementation class:

public class CPU {  
      
    public void startup(){  
        System.out.println("cpu startup!");  
    }  
      
    public void shutdown(){  
        System.out.println("cpu shutdown!");  
    }  
}  
public class Memory {  
      
    public void startup(){  
        System.out.println("memory startup!");  
    }  
      
    public void shutdown(){  
        System.out.println("memory shutdown!");  
    }  
}  
public class Disk {  
      
    public void startup(){  
        System.out.println("disk startup!");  
    }  
      
    public void shutdown(){  
        System.out.println("disk shutdown!");  
    }  
}  
public class Computer {  
    private CPU cpu;  
    private Memory memory;  
    private Disk disk;  
      
    public Computer(){  
        cpu = new CPU();  
        memory = new Memory();  
        disk = new Disk();  
    }  
      
    public void startup(){  
        System.out.println("start the computer!");  
        cpu.startup();  
        memory.startup();  
        disk.startup();  
        System.out.println("start computer finished!");  
    }  
      
    public void shutdown(){  
        System.out.println("begin to close the computer!");  
        cpu.shutdown();  
        memory.shutdown();  
        disk.shutdown();  
        System.out.println("computer closed!");  
    }  
}  

The User class is as follows:

public class User {  
  
    public static void main(String[] args) {  
        Computer computer = new Computer();  
        computer.startup();  
        computer.shutdown();  
    }  
}  

Output:
start the computer!
cpu startup!
memory startup!
disk startup!
start computer finished!
begin to close the computer!
cpu shutdown!
memory shutdown!
disk shutdown!
computer closed!
If we don't have a Computer class, then CPU, Memory and Disk will hold instances and have relationships with each other, which will cause serious dependence. Modifying one class may lead to the modification of other classes. This is not what we want to see. With a Computer class, the relationship between them is placed in the Computer class, which plays the role of understanding coupling. This is the appearance mode!

10. Bridge mode

Bridging mode is to separate things from their concrete implementation so that they can change independently. The purpose of bridging is to decouple abstraction and implementation, so that they can change independently. Like the commonly used JDBC bridge DriverManager, when JDBC connects to the database, it basically doesn't need to move too much code or even nothing. The reason is that JDBC provides a unified interface and each database provides its own implementation, Just use a program called database driver to bridge. Let's look at the diagram:

Implementation code:
Define the interface first:

public interface Sourceable {  
    public void method();  
}  

Define two implementation classes:

public class SourceSub1 implements Sourceable {  
  
    @Override  
    public void method() {  
        System.out.println("this is the first sub!");  
    }  
}  
public class SourceSub2 implements Sourceable {  
  
    @Override  
    public void method() {  
        System.out.println("this is the second sub!");  
    }  
}  

Define a bridge that holds an instance of Sourceable:

public abstract class Bridge {  
    private Sourceable source;  
  
    public void method(){  
        source.method();  
    }  
      
    public Sourceable getSource() {  
        return source;  
    }  
  
    public void setSource(Sourceable source) {  
        this.source = source;  
    }  
}  
public class MyBridge extends Bridge {  
    public void method(){  
        getSource().method();  
    }  
}  

Test class:

public class BridgeTest {  
      
    public static void main(String[] args) {  
          
        Bridge bridge = new MyBridge();  
          
        /*Call the first object*/  
        Sourceable source1 = new SourceSub1();  
        bridge.setSource(source1);  
        bridge.method();  
          
        /*Call the second object*/  
        Sourceable source2 = new SourceSub2();  
        bridge.setSource(source2);  
        bridge.method();  
    }  
} 

output:
this is the first sub!
this is the second sub!
In this way, through the call to the Bridge class, the calls to the implementation classes SourceSub1 and SourceSub2 of the interface Sourceable are realized. Next, I'll draw another diagram, and you should understand it, because this diagram is the principle of our JDBC connection. It has the basis of database learning, and you can understand it when combined.

11. Composite mode

The combination mode is sometimes called part whole mode, which is more convenient when dealing with problems similar to tree structure. Look at the relationship diagram:

Look directly at the code:

public class TreeNode {  
      
    private String name;  
    private TreeNode parent;  
    private Vector<TreeNode> children = new Vector<TreeNode>();  
      
    public TreeNode(String name){  
        this.name = name;  
    }  
  
    public String getName() {  
        return name;  
    }  
  
    public void setName(String name) {  
        this.name = name;  
    }  
  
    public TreeNode getParent() {  
        return parent;  
    }  
  
    public void setParent(TreeNode parent) {  
        this.parent = parent;  
    }  
      
    //Add child node  
    public void add(TreeNode node){  
        children.add(node);  
    }  
      
    //Delete child node  
    public void remove(TreeNode node){  
        children.remove(node);  
    }  
      
    //Get child node  
    public Enumeration<TreeNode> getChildren(){  
        return children.elements();  
    }  
}  
public class Tree {  
  
    TreeNode root = null;  
  
    public Tree(String name) {  
        root = new TreeNode(name);  
    }  
  
    public static void main(String[] args) {  
        Tree tree = new Tree("A");  
        TreeNode nodeB = new TreeNode("B");  
        TreeNode nodeC = new TreeNode("C");  
          
        nodeB.add(nodeC);  
        tree.root.add(nodeB);  
        System.out.println("build the tree finished!");  
    }  
}  

Usage scenario: combine multiple objects for operation. It is often used to represent tree structure, such as binary tree, number, etc.

12. Flyweight mode

The main purpose of shared meta mode is to realize the sharing of objects, that is, shared pool. When there are many objects in the system, it can reduce the memory overhead. It is usually used together with factory mode.

FlyWeightFactory is responsible for creating and managing meta units. When a client requests, the factory needs to check whether there are qualified objects in the current object pool. If so, it will return the existing objects. If not, it will create a new object. FlyWeight is a superclass. When it comes to the shared pool, we can easily think of the JDBC connection pool in Java. Considering the characteristics of each connection, it is not difficult to summarize that some objects suitable for sharing have some common attributes. Take the database connection pool for example, url, driverClassName, username, password and dbname. These attributes are the same for each connection, so it is suitable to use the shared meta mode, Build a factory class, take the above similar attributes as internal data and others as external data, and pass them in as parameters during method call, which saves space and reduces the number of instances.
Take an example:

Look at the code of database connection pool:

public class ConnectionPool {  
      
    private Vector<Connection> pool;  
      
    /*Public attribute*/  
    private String url = "jdbc:mysql://localhost:3306/test";  
    private String username = "root";  
    private String password = "root";  
    private String driverClassName = "com.mysql.jdbc.Driver";  
  
    private int poolSize = 100;  
    private static ConnectionPool instance = null;  
    Connection conn = null;  
  
    /*Construction method, do some initialization work*/  
    private ConnectionPool() {  
        pool = new Vector<Connection>(poolSize);  
  
        for (int i = 0; i < poolSize; i++) {  
            try {  
                Class.forName(driverClassName);  
                conn = DriverManager.getConnection(url, username, password);  
                pool.add(conn);  
            } catch (ClassNotFoundException e) {  
                e.printStackTrace();  
            } catch (SQLException e) {  
                e.printStackTrace();  
            }  
        }  
    }  
  
    /* Return connection to connection pool */  
    public synchronized void release() {  
        pool.add(conn);  
    }  
  
    /* Returns a database connection in the connection pool */  
    public synchronized Connection getConnection() {  
        if (pool.size() > 0) {  
            Connection conn = pool.get(0);  
            pool.remove(conn);  
            return conn;  
        } else {  
            return null;  
        }  
    }  
}  

Through the management of connection pool, the sharing of database connection is realized, and the connection does not need to be re created every time, which saves the cost of database re creation and improves the performance of the system! This chapter explains seven structural models. Due to the space problem, we will start another chapter on the remaining 11 behavioral models. Please pay attention to them!

This chapter is the last lecture on design patterns. We will talk about the third design pattern - behavior pattern, which has 11 kinds: strategy pattern, template method pattern, observer pattern, iteration sub pattern, responsibility chain pattern, command pattern, memo pattern, state pattern, visitor pattern, mediator pattern and Interpreter pattern. I've been writing about design patterns for a long time, and I'm halfway there. It's a time-consuming thing to write a blog, because I have to be responsible for the readers. I hope to write it as clearly as possible, whether it's graphics, code or expression, so that the readers can understand it. I think both I and readers want to see high-quality blog posts come out. Starting from myself, I'll stick to it and update it constantly, The driving force comes from the continuous support of readers and friends. I will try my best to write every article! I hope you can continue to give opinions and suggestions and jointly create a perfect blog!

Let's start with a diagram to see the relationship between these 11 modes:
Class 1: implemented through the relationship between parent and child classes. Class II: between two classes. The third category: the status of the class. Category 4: through intermediate classes

13. strategy

The policy pattern defines a series of algorithms and encapsulates each algorithm so that they can replace each other, and the change of the algorithm will not affect the customers using the algorithm. It is necessary to design an interface to provide a unified method for a series of implementation classes. Multiple implementation classes implement the interface, and design an abstract class (optional, belonging to auxiliary class) to provide auxiliary functions. The relationship diagram is as follows:

The ICalculator in the figure provides a method of consent,
AbstractCalculator is an auxiliary class that provides auxiliary methods. Next, implement each class in turn:
First, unify the interface:

public interface ICalculator {  
    public int calculate(String exp);  
}  

Auxiliary class:

public abstract class AbstractCalculator {  
      
    public int[] split(String exp,String opt){  
        String array[] = exp.split(opt);  
        int arrayInt[] = new int[2];  
        arrayInt[0] = Integer.parseInt(array[0]);  
        arrayInt[1] = Integer.parseInt(array[1]);  
        return arrayInt;  
    }  
}  

Three implementation classes:

public class Plus extends AbstractCalculator implements ICalculator {  
  
    @Override  
    public int calculate(String exp) {  
        int arrayInt[] = split(exp,"\\+");  
        return arrayInt[0]+arrayInt[1];  
    }  
}  
public class Minus extends AbstractCalculator implements ICalculator {  
  
    @Override  
    public int calculate(String exp) {  
        int arrayInt[] = split(exp,"-");  
        return arrayInt[0]-arrayInt[1];  
    }  
  
}  
public class Multiply extends AbstractCalculator implements ICalculator {  
  
    @Override  
    public int calculate(String exp) {  
        int arrayInt[] = split(exp,"\\*");  
        return arrayInt[0]*arrayInt[1];  
    }  
}  

Simple test class:

public class StrategyTest {  
  
    public static void main(String[] args) {  
        String exp = "2+8";  
        ICalculator cal = new Plus();  
        int result = cal.calculate(exp);  
        System.out.println(result);  
    }  
} 

Output: 10
The decision-making power of the policy mode lies with the user. The system itself provides the implementation of different algorithms, adds or deletes algorithms, and encapsulates various algorithms. Therefore, the policy mode is mostly used in the algorithm decision-making system, and the external user only needs to decide which algorithm to use.

14. Template Method

An abstract subclass of method can be defined by overriding a method in the main class, that is, an abstract subclass of the method can be defined by overriding the method in the main class, which can be explained as follows:

This is to define a main method calculate in the AbstractCalculator class. calculate() calls spit(). Plus and Minus inherit the AbstractCalculator class respectively and call subclasses through the call to AbstractCalculator. See the following example:

public abstract class AbstractCalculator {  
      
    /*Main method to call other methods of this class*/  
    public final int calculate(String exp,String opt){  
        int array[] = split(exp,opt);  
        return calculate(array[0],array[1]);  
    }  
      
    /*Methods overridden by subclasses*/  
    abstract public int calculate(int num1,int num2);  
      
    public int[] split(String exp,String opt){  
        String array[] = exp.split(opt);  
        int arrayInt[] = new int[2];  
        arrayInt[0] = Integer.parseInt(array[0]);  
        arrayInt[1] = Integer.parseInt(array[1]);  
        return arrayInt;  
    }  
}  
public class Plus extends AbstractCalculator {  
  
    @Override  
    public int calculate(int num1,int num2) {  
        return num1 + num2;  
    }  
}  

Test class:

public class StrategyTest {  
  
    public static void main(String[] args) {  
        String exp = "8+8";  
        AbstractCalculator cal = new Plus();  
        int result = cal.calculate(exp, "\\+");  
        System.out.println(result);  
    }  
}  

Let me trace the execution process of this applet: first, take exp and "\ +" as parameters, call the calculate(String,String) method in the AbstractCalculator class, call the same kind of split() in the calculate(String,String), and then call the calculate (int, int) method. From this method, enter the subclass. After executing return num1 + num2, return the value to the AbstractCalculator class, assign it to result, and print it out. It just verifies our initial idea.

15. Observer mode

The next four patterns, including this pattern, are the relationship between classes and do not involve inheritance. When learning, you should remember induction and the figure at the beginning of this article. The observer mode is easy to understand. It is similar to email subscription and RSS subscription. When we browse some blogs or wiki s, we often see RSS icons. This means that when you subscribe to the article, you will be notified in time if there are subsequent updates. In fact, simply put, when an object changes, other objects that depend on the object will be notified and change with it! There is a one to many relationship between objects. Let's first look at the diagram:

Let me explain the role of these classes: the MySubject class is our main object. Observer1 and Observer2 are objects that depend on MySubject. When MySubject changes, observer1 and Observer2 will inevitably change. AbstractSubject class defines the list of objects to be monitored, which can be modified: add or delete monitored objects, and notify the existing objects in the list when MySubject changes. Let's look at the implementation code:
An Observer interface:

public interface Observer {  
    public void update();  
}  

Two implementation classes:

public class Observer1 implements Observer {  
  
    @Override  
    public void update() {  
        System.out.println("observer1 has received!");  
    }  
} 
public class Observer2 implements Observer {  
  
    @Override  
    public void update() {  
        System.out.println("observer2 has received!");  
    }  
  
}  

Subject interface and implementation class:

public interface Subject {  
      
    /*Add observers*/  
    public void add(Observer observer);  
      
    /*Delete observer*/  
    public void del(Observer observer);  
      
    /*Notify all observers*/  
    public void notifyObservers();  
      
    /*Own operation*/  
    public void operation();  
}  
public abstract class AbstractSubject implements Subject {  
  
    private Vector<Observer> vector = new Vector<Observer>();  
    @Override  
    public void add(Observer observer) {  
        vector.add(observer);  
    }  
  
    @Override  
    public void del(Observer observer) {  
        vector.remove(observer);  
    }  
  
    @Override  
    public void notifyObservers() {  
        Enumeration<Observer> enumo = vector.elements();  
        while(enumo.hasMoreElements()){  
            enumo.nextElement().update();  
        }  
    }  
}  
public class MySubject extends AbstractSubject {  
  
    @Override  
    public void operation() {  
        System.out.println("update self!");  
        notifyObservers();  
    }  
  
}  

Test class:

public class ObserverTest {  
  
    public static void main(String[] args) {  
        Subject sub = new MySubject();  
        sub.add(new Observer1());  
        sub.add(new Observer2());  
          
        sub.operation();  
    }  
  
}  

Output:
update self!
observer1 has received!
observer2 has received!

16. Iterator

As the name suggests, iterator mode is to access the objects in the aggregation in sequence. Generally speaking, it is very common in the collection. If you are familiar with the collection class, it will be very easy to understand this mode. This sentence contains two meanings: one is the object to be traversed, that is, the aggregate object, and the other is the iterator object, which is used for traversal access to the aggregate object. Let's look at the diagram below:

This idea is exactly the same as that we commonly use. Some Collection operations are defined in MyCollection, and a series of iterative operations are defined in MyIterator. With Collection instances, let's take a look at the implementation code:
Two interfaces:

 
public interface Collection {  
      
    public Iterator iterator();  
      
    /*Get collection elements*/  
    public Object get(int i);  
      
    /*Get collection size*/  
    public int size();  
}  
public interface Iterator {  
    //Move forward  
    public Object previous();  
      
    //Move back  
    public Object next();  
    public boolean hasNext();  
      
    //Get the first element  
    public Object first();  
}  

Two implementations:

public class MyCollection implements Collection {  
  
    public String string[] = {"A","B","C","D","E"};  
    @Override  
    public Iterator iterator() {  
        return new MyIterator(this);  
    }  
  
    @Override  
    public Object get(int i) {  
        return string[i];  
    }  
  
    @Override  
    public int size() {  
        return string.length;  
    }  
}  
public class MyIterator implements Iterator {  
  
    private Collection collection;  
    private int pos = -1;  
      
    public MyIterator(Collection collection){  
        this.collection = collection;  
    }  
      
    @Override  
    public Object previous() {  
        if(pos > 0){  
            pos--;  
        }  
        return collection.get(pos);  
    }  
  
    @Override  
    public Object next() {  
        if(pos<collection.size()-1){  
            pos++;  
        }  
        return collection.get(pos);  
    }  
  
    @Override  
    public boolean hasNext() {  
        if(pos<collection.size()-1){  
            return true;  
        }else{  
            return false;  
        }  
    }  
  
    @Override  
    public Object first() {  
        pos = 0;  
        return collection.get(pos);  
    }  
  
}  

Test class:

 
public class Test {  
  
    public static void main(String[] args) {  
        Collection collection = new MyCollection();  
        Iterator it = collection.iterator();  
          
        while(it.hasNext()){  
            System.out.println(it.next());  
        }  
    }  
}  

Output: A B C D E
Here we seem to simulate the process of a collection class. Isn't it cool? In fact, all classes in JDK are also these basic things. Add some design patterns and some optimization together. As long as we learn and master these things, we can also write our own collection classes and even frameworks!

17. Chain of Responsibility

Next, we will talk about the responsibility chain model. There are multiple objects, and each object holds a reference to the next object. In this way, a chain will be formed, and the request will be passed on the chain until an object decides to process the request. However, the sender does not know which object will handle the request in the end. Therefore, the responsibility chain mode can be implemented to dynamically adjust the system while concealing the client. First look at the diagram:

Abstracthandler class provides get and set methods to facilitate MyHandle class to set and modify reference objects. MyHandle class is the core. After instantiation, a series of mutually held objects are generated to form a chain.

public interface Handler {  
    public void operator();  
}  
[java] view plaincopy
public abstract class AbstractHandler {  
      
    private Handler handler;  
  
    public Handler getHandler() {  
        return handler;  
    }  
  
    public void setHandler(Handler handler) {  
        this.handler = handler;  
    }  
      
} 
public class MyHandler extends AbstractHandler implements Handler {  
  
    private String name;  
  
    public MyHandler(String name) {  
        this.name = name;  
    }  
  
    @Override  
    public void operator() {  
        System.out.println(name+"deal!");  
        if(getHandler()!=null){  
            getHandler().operator();  
        }  
    }  
}  
public class Test {  
  
    public static void main(String[] args) {  
        MyHandler h1 = new MyHandler("h1");  
        MyHandler h2 = new MyHandler("h2");  
        MyHandler h3 = new MyHandler("h3");  
  
        h1.setHandler(h2);  
        h2.setHandler(h3);  
  
        h1.operator();  
    }  
} 

Output:
h1deal!
h2deal!
h3deal!
It is emphasized here that the request on the link can be a chain, a tree or a ring. The pattern itself does not restrict this, and we need to implement it ourselves. At the same time, at one time, the command is only allowed to be passed from one object to another, not to multiple objects.

18. Command mode (command)

The command mode is easy to understand. For example, the commander orders the soldiers to do something. From the perspective of the whole thing, the commander's role is to issue a password, which is transmitted to the soldiers' ears and executed by the soldiers. The good thing about this process is that the three are decoupled from each other. Neither party needs to rely on others. It just needs to do its own thing well. The commander wants the result and won't pay attention to how the soldiers achieve it. Let's look at the diagram:

Invoker is the caller (commander), Receiver is the callee (soldier), and MyCommand is the Command. It implements the Command interface and holds the receiving object. See the implementation code:

public interface Command {  
    public void exe();  
}  
public class MyCommand implements Command {  
  
    private Receiver receiver;  
      
    public MyCommand(Receiver receiver) {  
        this.receiver = receiver;  
    }  
  
    @Override  
    public void exe() {  
        receiver.action();  
    }  
} 
public class Receiver {  
    public void action(){  
        System.out.println("command received!");  
    }  
}  
public class Invoker {  
      
    private Command command;  
      
    public Invoker(Command command) {  
        this.command = command;  
    }  
  
    public void action(){  
        command.exe();  
    }  
}  
public class Test {  
  
    public static void main(String[] args) {  
        Receiver receiver = new Receiver();  
        Command cmd = new MyCommand(receiver);  
        Invoker invoker = new Invoker(cmd);  
        invoker.action();  
    }  
}  

Output: command received!
It is understood that the purpose of command mode is to decouple the sender and executor of the command and separate the request and execution. Students familiar with Struts should know that Struts is actually a technology that separates the request and presentation, which must involve the idea of command mode!

In fact, each design pattern is a very important idea. It looks familiar. In fact, it is because we are involved in what we have learned. Although sometimes we don't know, it is reflected everywhere in the design of Java itself, such as AWT, JDBC, collection class, IO pipeline or Web framework. Design patterns are everywhere. Because of our limited space, it is difficult to say that each design mode is very detailed, but I will try my best to write the meaning clearly in the limited space and space, so as to better let everyone understand. If there is no accident in this chapter, it should be the last part of the design pattern. First, let's go to the figure at the beginning of the previous article:

This chapter deals with the third and fourth categories.

19. Memo mode

The main purpose is to save a certain state of an object so that the object can be restored at an appropriate time. Personally, I think it is more vivid to call the backup mode. Generally speaking, suppose there is the original class A, which has various attributes, a can determine the attributes to be backed up, memo class B is used to store some internal states of a, and class C is used to store memos, which can only be stored and cannot be modified. Make a diagram to analyze:

The Original class is the Original class, which contains the attribute value to be saved and creates a memo class to save the value value. Memento class is a memo class, and Storage class is a class that stores memos. It holds instances of memento class. This pattern is well understood. Look directly at the source code:

public class Original {  
      
    private String value;  
      
    public String getValue() {  
        return value;  
    }  
  
    public void setValue(String value) {  
        this.value = value;  
    }  
  
    public Original(String value) {  
        this.value = value;  
    }  
  
    public Memento createMemento(){  
        return new Memento(value);  
    }  
      
    public void restoreMemento(Memento memento){  
        this.value = memento.getValue();  
    }  
}  
public class Memento {  
      
    private String value;  
  
    public Memento(String value) {  
        this.value = value;  
    }  
  
    public String getValue() {  
        return value;  
    }  
  
    public void setValue(String value) {  
        this.value = value;  
    }  
}  
public class Storage {  
      
    private Memento memento;  
      
    public Storage(Memento memento) {  
        this.memento = memento;  
    }  
  
    public Memento getMemento() {  
        return memento;  
    }  
  
    public void setMemento(Memento memento) {  
        this.memento = memento;  
    }  
}  

Test class:

public class Test {  
  
    public static void main(String[] args) {  
          
        // Create original class  
        Original origi = new Original("egg");  
  
        // Create memo  
        Storage storage = new Storage(origi.createMemento());  
  
        // Modify the status of the original class  
        System.out.println("The initialization status is:" + origi.getValue());  
        origi.setValue("niu");  
        System.out.println("The modified status is:" + origi.getValue());  
  
        // Reply to the status of the original class  
        origi.restoreMemento(storage.getMemento());  
        System.out.println("The status after recovery is:" + origi.getValue());  
    }  
}  

Output:
Initialization status: egg
The modified status is: niu
The restored status is: egg
A brief description: when creating a new original class, value is initialized to egg. After modification, the value is set to niu. Finally, the penultimate line is restored, and the result is successfully restored. In fact, I think this mode is called "backup restore" mode, which is the most vivid.

20. State mode

The core idea is: when the state of the object changes, it changes its behavior at the same time, which is easy to understand! Take QQ as an example. There are several States, such as online, invisible and busy. Each state corresponds to different operations, and your friends can also see your state. Therefore, there are two state modes: 1. Different behaviors can be obtained by changing the state. 2. Your friends can see your changes at the same time. Look at the picture:

State class is a state class, and Context class can realize switching. Let's take a look at the code:

package com.xtfggef.dp.state;  
  
/** 
 * Core class of state class 
 * 2012-12-1 
 * @author erqing 
 * 
 */  
public class State {  
      
    private String value;  
      
    public String getValue() {  
        return value;  
    }  
  
    public void setValue(String value) {  
        this.value = value;  
    }  
  
    public void method1(){  
        System.out.println("execute the first opt!");  
    }  
      
    public void method2(){  
        System.out.println("execute the second opt!");  
    }  
}  
package com.xtfggef.dp.state;  
  
/** 
 * State mode switching class 2012-12-1 
 * @author erqing 
 *  
 */  
public class Context {  
  
    private State state;  
  
    public Context(State state) {  
        this.state = state;  
    }  
  
    public State getState() {  
        return state;  
    }  
  
    public void setState(State state) {  
        this.state = state;  
    }  
  
    public void method() {  
        if (state.getValue().equals("state1")) {  
            state.method1();  
        } else if (state.getValue().equals("state2")) {  
            state.method2();  
        }  
    }  
}  

Test class:

 
public class Test {  
  
    public static void main(String[] args) {  
          
        State state = new State();  
        Context context = new Context(state);  
          
        //Set the first state  
        state.setValue("state1");  
        context.method();  
          
        //Set the second state  
        state.setValue("state2");  
        context.method();  
    }  
}  

Output:

execute the first opt!
execute the second opt!
According to this feature, the state mode is widely used in daily development, especially when making websites. Sometimes we want to distinguish some of their functions according to a certain attribute of the object, such as simple permission control.

21. Visitor mode

Visitor mode decouples the data structure from the operations acting on the structure, so that the set of operations can evolve relatively freely. Visitor mode is suitable for systems with relatively stable data structure and easy to change algorithm. Because the visitor mode makes it easy to increase the operation of the algorithm. If the system data structure object is easy to change and new data objects are often added, it is not suitable to use the visitor mode. The advantage of visitor mode is that adding operations is easy, because adding operations means adding new visitors. Visitor pattern centralizes relevant behaviors into a visitor object, and its change does not affect the system data structure. The disadvantage is that it is difficult to add new data structures—— From Encyclopedia
In short, visitor mode is a method to separate object data structure and behavior. Through this separation, we can dynamically add new operations for a visitor without making other modifications. Simple diagram:

Let's look at the original code: a Visitor class that stores the objects to be accessed,

public interface Visitor {  
    public void visit(Subject sub);  
} 
public class MyVisitor implements Visitor {  
  
    @Override  
    public void visit(Subject sub) {  
        System.out.println("visit the subject: "+sub.getSubject());  
    }  
}  

Subject class, accept method, accept the object to be accessed, getSubject() gets the attribute to be accessed,

public interface Subject {  
    public void accept(Visitor visitor);  
    public String getSubject();  
}  
public class MySubject implements Subject {  
  
    @Override  
    public void accept(Visitor visitor) {  
        visitor.visit(this);  
    }  
  
    @Override  
    public String getSubject() {  
        return "love";  
    }  
}  

Test:

public class Test {  
  
    public static void main(String[] args) {  
          
        Visitor visitor = new MyVisitor();  
        Subject sub = new MySubject();  
        sub.accept(visitor);      
    }  
}  

Output: visit the subject: love

Applicable scenario of this mode: if we want to add new functions to an existing class, we have to consider several things: 1. Will the new functions have compatibility problems with the existing functions? 2. Will it need to be added in the future? 3. What if the class does not allow code modification? The best solution to these problems is to use visitor mode, which is suitable for systems with relatively stable data structure and decouples the data structure from the algorithm,

22. Mediator mode

The Mediator mode is also used to reduce the coupling between classes, because if there are dependencies between classes, it is not conducive to the expansion and maintenance of functions, because as long as one object is modified, other associated objects have to be modified. If you use the Mediator mode, you only need to care about the relationship with the Mediator class, the relationship between specific classes and scheduling to the Mediator, which is a bit like the role of the spring container. First look at the picture:

Class myuser1 and class myuser2 are used to implement the decoupling between the two classes. In fact, class myuser1 and class myuser2 are used to implement the decoupling of the two classes. In this way, class myuser1 and class myuser2 are used to control each other. In this way, User1 and User2 objects are independent of each other. They only need to maintain the relationship with Mediator. The rest is maintained by MyMediator class! Basic implementation:

public interface Mediator {  
    public void createMediator();  
    public void workAll();  
} 
public class MyMediator implements Mediator {  
  
    private User user1;  
    private User user2;  
      
    public User getUser1() {  
        return user1;  
    }  
  
    public User getUser2() {  
        return user2;  
    }  
  
    @Override  
    public void createMediator() {  
        user1 = new User1(this);  
        user2 = new User2(this);  
    }  
  
    @Override  
    public void workAll() {  
        user1.work();  
        user2.work();  
    }  
}  
public abstract class User {  
      
    private Mediator mediator;  
      
    public Mediator getMediator(){  
        return mediator;  
    }  
      
    public User(Mediator mediator) {  
        this.mediator = mediator;  
    }  
  
    public abstract void work();  
}  
public class User1 extends User {  
  
    public User1(Mediator mediator){  
        super(mediator);  
    }  
      
    @Override  
    public void work() {  
        System.out.println("user1 exe!");  
    }  
}  
public class User2 extends User {  
  
    public User2(Mediator mediator){  
        super(mediator);  
    }  
      
    @Override  
    public void work() {  
        System.out.println("user2 exe!");  
    }  
} 

Test class:

public class Test {  
  
    public static void main(String[] args) {  
        Mediator mediator = new MyMediator();  
        mediator.createMediator();  
        mediator.workAll();  
    }  
} 

Output:
user1 exe!
user2 exe!

23. Interpreter mode

Interpreter mode is our last lecture temporarily. It is generally mainly used in the development of compiler in OOP development, so its application scope is relatively narrow.

Context class is a context environment class. Plus and Minus are the implementation of calculation respectively. The code is as follows:

public interface Expression {  
    public int interpret(Context context);  
}  
public class Plus implements Expression {  
  
    @Override  
    public int interpret(Context context) {  
        return context.getNum1()+context.getNum2();  
    }  
}  
public class Minus implements Expression {  
  
    @Override  
    public int interpret(Context context) {  
        return context.getNum1()-context.getNum2();  
    }  
}  
public class Context {  
      
    private int num1;  
    private int num2;  
      
    public Context(int num1, int num2) {  
        this.num1 = num1;  
        this.num2 = num2;  
    }  
      
    public int getNum1() {  
        return num1;  
    }  
    public void setNum1(int num1) {  
        this.num1 = num1;  
    }  
    public int getNum2() {  
        return num2;  
    }  
    public void setNum2(int num2) {  
        this.num2 = num2;  
    }  
      
      
}  
public class Test {  
  
    public static void main(String[] args) {  
  
        // Calculate the value of 9 + 2-8  
        int result = new Minus().interpret((new Context(new Plus()  
                .interpret(new Context(9, 2)), 8)));  
        System.out.println(result);  
    }  
} 

Finally, output the correct result: 3.

Tags: Design Pattern

Posted by bigdaddysheikh on Fri, 20 May 2022 07:31:59 +0300