25,000 words explain 23 design patterns (multiple pictures + code)

25,000 words explain 23 design patterns (multiple pictures + code)

Table of contents

  • Creational patterns
  • structural pattern
  • behavioral model
  • Summarize

picture

foreword

I have always wanted to write an article introducing design patterns, so that readers can read it quickly, understand it at a glance, use it after reading it, and not confuse the various patterns.

Design patterns are a high-level abstract summary of various codes written in your actual work. The most famous of them is the Gang of Four ( GoF ) classification. They classify design patterns into 23 classic patterns. According to the purpose We can be divided into three categories, respectively, creational mode, structural mode and behavioral mode.

There are some important design principles to share with you at the beginning, and these principles will run through the full text:

  1. Program to an interface, not an implementation. This is very important and the first step of elegant and scalable code, so there is no need to say more.
  2. The principle of single responsibility. Each class should have only a single functionality, and that functionality should be fully encapsulated by the class.
  3. Closed for modification, open for extension. Closing the modification means that we have worked hard to write the code, the functions that should be implemented and the bugs that should be fixed have been completed, and others can’t change it just by saying it; it is easier to understand when it is open to extensions, that is to say, in Based on the code we wrote, it is easy to expand.

Creational patterns are simpler, but less interesting, while structural and behavioral patterns are more interesting.

**
**

Creational patterns

The role of the creational pattern is to create objects. When it comes to creating an object, the most familiar one is to create a new object and then set related attributes. However, in many scenarios, we need to provide clients with a more friendly way to create objects, especially when we define classes but need to provide them to other developers.

Simple Factory Pattern

As simple as the name, very simple, just go to the code:

public class FoodFactory {

    public static Food makeFood(String name) {
        if (name.equals("noodle")) {
            Food noodle = new LanZhouNoodle();
            noodle.addSpicy("more");
            return noodle;
        } else if (name.equals("chicken")) {
            Food chicken = new HuangMenChicken();
            chicken.addCondiment("potato");
            return chicken;
        } else {
            return null;
        }
    }
}

Among them, LanZhouNoodle and HuangMenChicken both inherit from Food.

Simply put, the simple factory pattern is usually like this. A factory class XxxFactory has a static method in it, which returns different instance objects derived from the same parent class (or implementing the same interface) according to our different parameters.

We emphasize the principle of single responsibility , a class provides only one function, and the function of FoodFactory is only responsible for producing various kinds of Food.

factory pattern

The simple factory model is very simple. If it can meet our needs, I don't think we need to bother. The reason why we need to introduce the factory pattern is because we often need to use two or more factories.

public interface FoodFactory {
    Food makeFood(String name);
}
public class ChineseFoodFactory implements FoodFactory {

    @Override
    public Food makeFood(String name) {
        if (name.equals("A")) {
            return new ChineseFoodA();
        } else if (name.equals("B")) {
            return new ChineseFoodB();
        } else {
            return null;
        }
    }
}
public class AmericanFoodFactory implements FoodFactory {

    @Override
    public Food makeFood(String name) {
        if (name.equals("A")) {
            return new AmericanFoodA();
        } else if (name.equals("B")) {
            return new AmericanFoodB();
        } else {
            return null;
        }
    }
}

Among them, ChineseFoodA, ChineseFoodB, AmericanFoodA, AmericanFoodB are all derived from Food.

Client call:

public class APP {
    public static void main(String[] args) {
        // 先选择一个具体的工厂
        FoodFactory factory = new ChineseFoodFactory();
        // 由第一步的工厂产生具体的对象,不同的工厂造出不一样的对象
        Food food = factory.makeFood("A");
    }
}

Although they all call makeFood("A") to make food of type A, the products produced by different factories are completely different.

In the first step, we need to select the appropriate factory, and then the second step is basically the same as the simple factory.

The core is that we need to select the factory we need in the first step . For example, we have the LogFactory interface, and the implementation classes include FileLogFactory and KafkaLogFactory, which correspond to writing logs to files and Kafka respectively. Obviously, the first step of our client needs to decide whether to instantiate FileLogFactory or KafkaLogFactory, which will determine later of all operations.

Although it is simple, I also draw all the components on a picture so that readers can see it more clearly:

picture

abstract factory pattern

When it comes to product families , the abstract factory pattern needs to be introduced.

A classic example is building a computer. Let's not introduce the abstract factory pattern first, and see how to implement it.

Because a computer is composed of many components, we abstract the CPU and the motherboard, and then the CPU is produced by the CPUFactory, and the motherboard is produced by the MainBoardFactory. Then, we combine the CPU and the motherboard, as shown in the following figure:

picture

The client call at this time is as follows:

// 得到 Intel 的 CPU
CPUFactory cpuFactory = new IntelCPUFactory();
CPU cpu = intelCPUFactory.makeCPU();

// 得到 AMD 的主板
MainBoardFactory mainBoardFactory = new AmdMainBoardFactory();
MainBoard mainBoard = mainBoardFactory.make();

// 组装 CPU 和主板
Computer computer = new Computer(cpu, mainBoard);

Looking at the CPU factory and motherboard factory separately, they are the factory models we mentioned earlier . This method is also easy to expand, because if you want to add a hard disk to the computer, you only need to add a HardDiskFactory and the corresponding implementation, without modifying the existing factory.

However, there is a problem with this method, that is, if the CPU produced by Intel and the motherboard produced by AMD are not compatible , then this code is prone to errors, because the client does not know that they are not compatible, and a random combination will appear by mistake. .

The following is the concept of the product family we are talking about , which represents a collection of a series of accessories that make up a product:

picture

When it comes to this kind of product family, the abstract factory pattern is needed to support it. We no longer define CPU factories, motherboard factories, hard disk factories, display factories, etc., we directly define computer factories, and each computer factory is responsible for producing all equipment, so that there must be no compatibility problems.

picture

At this time, for the client, it is no longer necessary to select CPU manufacturers, motherboard manufacturers, hard disk manufacturers, etc. directly, and directly choose a brand factory, which will be responsible for producing all things, and can guarantee that they are compatible and usable.

public static void main(String[] args) {
    // 第一步就要选定一个“大厂”
    ComputerFactory cf = new AmdFactory();
    // 从这个大厂造 CPU
    CPU cpu = cf.makeCPU();
    // 从这个大厂造主板
    MainBoard board = cf.makeMainBoard();
      // 从这个大厂造硬盘
      HardDisk hardDisk = cf.makeHardDisk();

    // 将同一个厂子出来的 CPU、主板、硬盘组装在一起
    Computer result = new Computer(cpu, board, hardDisk);
}

Of course, the problem of the abstract factory is also obvious. For example, if we want to add a display, we need to modify all the factories and add the method of manufacturing displays to all the factories. This is a bit of a violation of the design principle of being closed for modification and open for extension .

singleton pattern

The singleton pattern is used the most and makes the most mistakes.

Hungry man mode is the simplest:

public class Singleton {
    // 首先,将 new Singleton() 堵死
    private Singleton() {};
    // 创建私有静态实例,意味着这个类第一次使用的时候就会进行创建
    private static Singleton instance = new Singleton();

    public static Singleton getInstance() {
        return instance;
    }
    // 瞎写一个静态方法。这里想说的是,如果我们只是要调用 Singleton.getDate(...),
    // 本来是不想要生成 Singleton 实例的,不过没办法,已经生成了
    public static Date getDate(String mode) {return new Date();}
}

Many people can tell the shortcomings of the hungry man mode, but I think it is rare to encounter such a situation in the production process: you define a singleton class and do not need its instance, but you put one or several of your The static methods that will be used are stuffed into this class.

The full man mode is the most error-prone:

public class Singleton {
    // 首先,也是先堵死 new Singleton() 这条路
    private Singleton() {}
    // 和饿汉模式相比,这边不需要先实例化出来,注意这里的 volatile,它是必须的
    private static volatile Singleton instance = null;

    public static Singleton getInstance() {
        if (instance == null) {
            // 加锁
            synchronized (Singleton.class) {
                // 这一次判断也是必须的,不然会有并发问题
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

Double check refers to checking whether the instance is null twice.

volatile is needed here, and I hope it will draw readers' attention.

Many people don't know how to write it, and directly add synchronized to the signature of the getInstance() method. Not much to say, the performance is too poor.

Nested classes are the most classic, and everyone will use them in the future:

public class Singleton3 {

    private Singleton3() {}
    // 主要是使用了 嵌套类可以访问外部类的静态属性和静态方法 的特性
    private static class Holder {
        private static Singleton3 instance = new Singleton3();
    }
    public static Singleton3 getInstance() {
        return Holder.instance;
    }
}

Note that many people will refer to this nested class as a static inner class . Strictly speaking, inner classes and nested classes are different, and the permissions of the outer classes they can access are also different.

Finally, let's talk about enumeration. Enumeration is very special. It initializes all the instances in it when the class is loaded, and the JVM guarantees that they will not be instantiated again, so it is a singleton by nature.

Although we rarely see enumerations used to implement singletons, in the source code of RxJava, enumerations are used to implement singletons in many places.

builder mode

The XxxBuilder classes that are often encountered are usually the product of the builder pattern. There are actually many variants of the builder mode, but for the client, we usually use the same mode:

Food food = new FoodBuilder().a().b().c().build();
Food food = Food.builder().a().b().c().build();

The routine is to first create a new Builder, then call a bunch of methods in a chain, and finally call the build() method again, and the object we need will be there.

Come to a regular builder model:

class User {
    // 下面是“一堆”的属性
    private String name;
    private String password;
    private String nickName;
    private int age;

    // 构造方法私有化,不然客户端就会直接调用构造方法了
    private User(String name, String password, String nickName, int age) {
        this.name = name;
        this.password = password;
        this.nickName = nickName;
        this.age = age;
    }
    // 静态方法,用于生成一个 Builder,这个不一定要有,不过写这个方法是一个很好的习惯,
    // 有些代码要求别人写 new User.UserBuilder().a()...build() 看上去就没那么好
    public static UserBuilder builder() {
        return new UserBuilder();
    }

    public static class UserBuilder {
        // 下面是和 User 一模一样的一堆属性
        private String  name;
        private String password;
        private String nickName;
        private int age;

        private UserBuilder() {
        }

        // 链式调用设置各个属性值,返回 this,即 UserBuilder
        public UserBuilder name(String name) {
            this.name = name;
            return this;
        }

        public UserBuilder password(String password) {
            this.password = password;
            return this;
        }

        public UserBuilder nickName(String nickName) {
            this.nickName = nickName;
            return this;
        }

        public UserBuilder age(int age) {
            this.age = age;
            return this;
        }

        // build() 方法负责将 UserBuilder 中设置好的属性“复制”到 User 中。
        // 当然,可以在 “复制” 之前做点检验
        public User build() {
            if (name == null || password == null) {
                throw new RuntimeException("用户名和密码必填");
            }
            if (age <= 0 || age >= 150) {
                throw new RuntimeException("年龄不合法");
            }
            // 还可以做赋予”默认值“的功能
              if (nickName == null) {
                nickName = name;
            }
            return new User(name, password, nickName, age);
        }
    }
}

The core is: first set all properties to Builder, and then copy these properties to the actual generated object when building () method.

Look at the client call:

public class APP {
    public static void main(String[] args) {
        User d = User.builder()
                .name("foo")
                .password("pAss12345")
                .age(25)
                .build();
    }
}

To be honest, the chained writing method of the builder mode is very attractive, but after writing a lot of "useless" builder code, I feel that this mode is useless. However, when there are many attributes, and some are required and some are optional, this mode will make the code much clearer. We can force the caller to provide required fields in the Builder's construction method . Also, the code is more elegant to verify each parameter in the build() method than in the User's construction method.

As an aside, readers are strongly recommended to use lombok. After using lombok, a lot of code above will become as follows:

@Builderclass User {    private String  name;    private String password;    private String nickName;    private int age;}

How about it, can you do something else with the time you save?

Of course, if you just want chain writing and don’t want the builder mode, there is a very simple way. The getter method of User remains unchanged, and all the setter methods let it return this , and then it can be like the following transfer:

User user = new User().setName("").setPassword("").setAge(20);

Many people use it like this, but the author thinks that this way of writing is very inelegant and is not recommended.

prototype pattern

This is the last design pattern of the creational pattern that I want to talk about.

The prototype mode is very simple: there is a prototype instance , and a new instance is generated based on this prototype instance, that is, "cloning".

There is a clone() method in the Object class, which is used to generate a new object. Of course, if we want to call this method, java requires our class to implement the Cloneable interface first . This interface does not define any methods, but it does not do so If so, when clone(), CloneNotSupportedException will be thrown.

protected native Object clone() throws CloneNotSupportedException;

Java's cloning is shallow cloning. When an object reference is encountered, the cloned object and the reference in the original object will point to the same object. The usual way to implement deep cloning is to serialize the object and then deserialize it.

I think it is enough to understand the prototype mode here. It is meaningless to say that this code or that code is a prototype mode in various ways.

Creational Patterns Summary

Creational patterns are generally relatively simple. Their function is to generate instance objects, which is the first step in various tasks. Because we write object-oriented code, our first step is of course to create an object.

The simple factory model is the simplest; the factory model adds the dimension of selecting a factory on the basis of the simple factory model, and the first step is to choose a suitable factory; the abstract factory model has the concept of a product family. If each product has compatibility problems, Use the abstract factory pattern. Not to mention the singleton mode. In order to ensure that the same object is used globally, on the one hand, it is for security reasons, and on the other hand, it is to save resources; the builder mode is specially designed to deal with classes with many attributes, in order to make the code more beautiful; the prototype mode It is used at least, just understand the knowledge related to the clone() method in the Object class.

structural pattern

The previous creational pattern introduced some design patterns for creating objects. The structural pattern introduced in this section aims to achieve the purpose of decoupling by changing the code structure, making our code easy to maintain and expand.

Proxy mode

The first proxy mode to be introduced is one of the most commonly used modes. A proxy is used to hide the implementation details of the concrete implementation class, and is usually used to add some logic before and after the real implementation.

Since it is a proxy , it is necessary to hide the real implementation from the client, and the proxy is responsible for all requests from the client. Of course, the proxy is just a proxy, it will not complete the actual business logic, but just a layer of skin, but for the client, it must behave as the real implementation of the client's needs.

Understand the word agent , this model is actually simple.

public interface FoodService {
    Food makeChicken();
    Food makeNoodle();
}

public class FoodServiceImpl implements FoodService {
    public Food makeChicken() {
          Food f = new Chicken()
        f.setChicken("1kg");
          f.setSpicy("1g");
          f.setSalt("3g");
        return f;
    }
    public Food makeNoodle() {
        Food f = new Noodle();
        f.setNoodle("500g");
        f.setSalt("5g");
        return f;
    }
}

// 代理要表现得“就像是”真实实现类,所以需要实现 FoodService
public class FoodServiceProxy implements FoodService {

    // 内部一定要有一个真实的实现类,当然也可以通过构造方法注入
    private FoodService foodService = new FoodServiceImpl();

    public Food makeChicken() {
        System.out.println("我们马上要开始制作鸡肉了");

        // 如果我们定义这句为核心代码的话,那么,核心代码是真实实现类做的,
        // 代理只是在核心代码前后做些“无足轻重”的事情
        Food food = foodService.makeChicken();

        System.out.println("鸡肉制作完成啦,加点胡椒粉"); // 增强
          food.addCondiment("pepper");

        return food;
    }
    public Food makeNoodle() {
        System.out.println("准备制作拉面~");
        Food food = foodService.makeNoodle();
        System.out.println("制作完成啦")
        return food;
    }
}

Client calls, note that we need to use a proxy to instantiate the interface:

// 这里用代理类来实例化
FoodService foodService = new FoodServiceProxy();
foodService.makeChicken();

picture

We found no, the proxy mode is to do "method packaging" or "method enhancement" . In aspect-oriented programming, it is actually the process of dynamic proxy. For example, in Spring, we don’t define proxy classes ourselves, but Spring will help us define the proxy dynamically, and then dynamically add the code logic we defined in @Before, @After, @Around to the proxy.

Speaking of dynamic proxy, it can be expanded that there are two types of dynamic proxy in Spring. One is that if our class defines an interface, such as the UserService interface and UserServiceImpl implementation, then the dynamic proxy of JDK is used. Interested readers can go to Look at the source code of the java.lang.reflect.Proxy class; the other is that we have not defined the interface ourselves, and Spring will use CGLIB for dynamic proxying. It is a jar package with good performance.

adapter pattern

After talking about the proxy mode and the adapter mode, it is because they are very similar, and a comparison can be made here.

What the adapter mode does is that there is an interface that needs to be implemented, but our ready-made objects are not satisfied, and a layer of adapters needs to be added for adaptation.

Generally speaking, there are three types of adapter patterns: default adapter pattern, object adapter pattern, and class adapter pattern. Don't rush to distinguish these few, let's look at the examples first.

(1) Default adapter mode

First, let's take a look at the simplest adapter mode, the default adapter mode (Default Adapter) .

We use the FileAlterationListener in the Appache commons-io package as an example. This interface defines many methods for monitoring files or folders. Once a corresponding operation occurs, the corresponding method will be triggered.

public interface FileAlterationListener {
    void onStart(final FileAlterationObserver observer);
    void onDirectoryCreate(final File directory);
    void onDirectoryChange(final File directory);
    void onDirectoryDelete(final File directory);
    void onFileCreate(final File file);
    void onFileChange(final File file);
    void onFileDelete(final File file);
    void onStop(final FileAlterationObserver observer);
}

A big problem with this interface is that there are too many abstract methods. If we want to use this interface, it means that we have to implement every abstract method. If we just want to monitor the file creation and file deletion events in the folder, but we still Having to implement all methods is obviously not what we want.

Therefore, we need the following adapter , which is used to implement the above interface, but all methods are empty methods , so that we can instead define our own class to inherit the following class.

public class FileAlterationListenerAdaptor implements FileAlterationListener {

    public void onStart(final FileAlterationObserver observer) {
    }

    public void onDirectoryCreate(final File directory) {
    }

    public void onDirectoryChange(final File directory) {
    }

    public void onDirectoryDelete(final File directory) {
    }

    public void onFileCreate(final File file) {
    }

    public void onFileChange(final File file) {
    }

    public void onFileDelete(final File file) {
    }

    public void onStop(final FileAlterationObserver observer) {
    }
}

For example, we can define the following classes, we only need to implement the methods we want to implement:

public class FileMonitor extends FileAlterationListenerAdaptor {
    public void onFileCreate(final File file) {
        // 文件创建
        doSomething();
    }

    public void onFileDelete(final File file) {
        // 文件删除
        doSomething();
    }
}

Of course, the above is just one of the adapter modes, and it is also the simplest one, so there is no need to say more. Next, introduce the "orthodox" adapter pattern.

(2) Object adapter mode

Let's take a look at an example in "Head First Design Patterns". I slightly modified it to see how to adapt a chicken to a duck, so that the chicken can also be used as a duck. Because, now duck this interface, we do not have a suitable implementation class to use, so an adapter is needed.

public interface Duck {
    public void quack(); // 鸭的呱呱叫
    public void fly(); // 飞
}

public interface Cock {
    public void gobble(); // 鸡的咕咕叫
    public void fly(); // 飞
}

public class WildCock implements Cock {
    public void gobble() {
        System.out.println("咕咕叫");
    }
    public void fly() {
        System.out.println("鸡也会飞哦");
    }
}

The duck interface has two methods, fly() and quare(). If the chicken Cock wants to pretend to be a duck, the fly() method is ready-made, but the chicken cannot quack like a duck, so there is no quack() method. At this time, you need to adapt:

// 毫无疑问,首先,这个适配器肯定需要 implements Duck,这样才能当做鸭来用
public class CockAdapter implements Duck {

    Cock cock;
    // 构造方法中需要一个鸡的实例,此类就是将这只鸡适配成鸭来用
      public CockAdapter(Cock cock) {
        this.cock = cock;
    }

    // 实现鸭的呱呱叫方法
    @Override
      public void quack() {
        // 内部其实是一只鸡的咕咕叫
        cock.gobble();
    }

      @Override
      public void fly() {
        cock.fly();
    }
}

The client call is as simple as:

public static void main(String[] args) {    // 有一只野鸡      Cock wildCock = new WildCock();      // 成功将野鸡适配成鸭      Duck duck = new CockAdapter(wildCock);      ...}

At this point, everyone knows what the adapter mode is all about. It is nothing more than that we need a duck, but we only have a chicken. At this time, we need to define an adapter, which acts as a duck, but the method in the adapter is still implemented by the chicken.

Let's use a diagram to illustrate briefly:

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-sX6OO6Ec-1683340130656)(data:image/svg+xml,%3C%3Fxml version='1.0' encoding= 'UTF-8'%3F%3E%3Csvg width='1px' height='1px' viewBox='0 0 1 1' version='1.1' xmlns='http://www.w3.org/2000/svg ' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Ctitle%3E%3C/title%3E%3Cg stroke='none' stroke-width='1' fill='none ' fill-rule='evenodd' fill-opacity='0'%3E%3Cg transform='translate(-249.000000, -126.000000)]' fill='%23FFFFFF'%3E%3Crect x='249' y=' 126' width='1' height='1'%3E%3C/rect%3E%3C/g%3E%3C/g%3E%3C/svg%3E)

The above picture should be easy to understand, so I won't explain more. Next, let's see how the class adaptation mode works.

(3) class adapter mode

Without further ado, let’s go directly to the picture:

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-JwKJA0vi-1683340130672)(data:image/svg+xml,%3C%3Fxml version='1.0' encoding= 'UTF-8'%3F%3E%3Csvg width='1px' height='1px' viewBox='0 0 1 1' version='1.1' xmlns='http://www.w3.org/2000/svg ' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Ctitle%3E%3C/title%3E%3Cg stroke='none' stroke-width='1' fill='none ' fill-rule='evenodd' fill-opacity='0'%3E%3Cg transform='translate(-249.000000, -126.000000)]' fill='%23FFFFFF'%3E%3Crect x='249' y=' 126' width='1' height='1'%3E%3C/rect%3E%3C/g%3E%3C/g%3E%3C/svg%3E)

Seeing this picture, it should be easy for everyone to understand. Through the method of inheritance, the adapter automatically obtains most of the methods it needs. At this time, the client is easier to use and can be used Target t = new SomeAdapter();directly .

(4) Summary of Adapter Mode

  1. Similarities and differences between class adaptation and object adaptation

    One uses inheritance and one uses composition;

    Class adaptation belongs to static implementation, object adaptation belongs to dynamic implementation of combination, and object adaptation needs to instantiate one more object.

    Generally speaking, object adaptation is used more.

  2. Similarities and Differences Between Adapter Pattern and Proxy Pattern

    Comparing these two modes is actually comparing the object adapter mode and the proxy mode. In terms of code structure, they are very similar, and both require a specific instance of the implementation class. But their purposes are different. The proxy mode is to enhance the original method; the adapter is to adapt, in order to provide "package the chicken into a duck, and then use it as a duck", while the chicken and duck are There was no inheritance relationship between them.

    picture

bridge mode

Understanding the bridge pattern is actually understanding code abstraction and decoupling.

We first need a bridge, which is an interface that defines the provided interface methods.

public interface DrawAPI {   public void draw(int radius, int x, int y);}

Then a series of implementation classes:

public class RedPen implements DrawAPI {
    @Override
    public void draw(int radius, int x, int y) {
        System.out.println("用红色笔画图,radius:" + radius + ", x:" + x + ", y:" + y);
    }
}
public class GreenPen implements DrawAPI {
    @Override
    public void draw(int radius, int x, int y) {
        System.out.println("用绿色笔画图,radius:" + radius + ", x:" + x + ", y:" + y);
    }
}
public class BluePen implements DrawAPI {
    @Override
    public void draw(int radius, int x, int y) {
        System.out.println("用蓝色笔画图,radius:" + radius + ", x:" + x + ", y:" + y);
    }
}

Define an abstract class, all implementation classes of this class need to use DrawAPI:

public abstract class Shape {
    protected DrawAPI drawAPI;
    protected Shape(DrawAPI drawAPI) {
        this.drawAPI = drawAPI;
    }
    public abstract void draw();
}

Define a subclass of an abstract class:

// 圆形
public class Circle extends Shape {
    private int radius;
    public Circle(int radius, DrawAPI drawAPI) {
        super(drawAPI);
        this.radius = radius;
    }
    public void draw() {
        drawAPI.draw(radius, 0, 0);
    }
}
// 长方形
public class Rectangle extends Shape {
    private int x;
    private int y;
    public Rectangle(int x, int y, DrawAPI drawAPI) {
        super(drawAPI);
        this.x = x;
        this.y = y;
    }
    public void draw() {
        drawAPI.draw(0, x, y);
    }
}

Finally, let's look at the client demo:

public static void main(String[] args) {
    Shape greenCircle = new Circle(10, new GreenPen());
    Shape redRectangle = new Rectangle(4, 8, new RedPen());
    greenCircle.draw();
    redRectangle.draw();
}

Maybe you can see that the above steps are not particularly clear, I have integrated all the things into one picture:

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-X5OYsYvf-1683340130676)(data:image/svg+xml,%3C%3Fxml version='1.0' encoding= 'UTF-8'%3F%3E%3Csvg width='1px' height='1px' viewBox='0 0 1 1' version='1.1' xmlns='http://www.w3.org/2000/svg ' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Ctitle%3E%3C/title%3E%3Cg stroke='none' stroke-width='1' fill='none ' fill-rule='evenodd' fill-opacity='0'%3E%3Cg transform='translate(-249.000000, -126.000000)]' fill='%23FFFFFF'%3E%3Crect x='249' y=' 126' width='1' height='1'%3E%3C/rect%3E%3C/g%3E%3C/g%3E%3C/svg%3E)

This time everyone should know where the abstraction is and how to decouple it. The advantage of the bridge model is also obvious, that is, it is very easy to expand.

This section cites the examples here and modifies them.

decorator pattern

It is not easy to explain the decoration mode clearly. Maybe readers know that several classes in Java IO are typical applications of decoration patterns, but readers may not be clear about the relationship among them, and maybe they forget after reading it. I hope that after reading this section, readers can have a deeper understanding of it.

First of all, let's look at a simple diagram. When looking at this diagram, it is enough to understand the hierarchical structure:

[External link image transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the image and upload it directly (img-CJdRx1yo-1683340130677)(data:image/svg+xml,%3C%3Fxml version='1.0' encoding= 'UTF-8'%3F%3E%3Csvg width='1px' height='1px' viewBox='0 0 1 1' version='1.1' xmlns='http://www.w3.org/2000/svg ' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Ctitle%3E%3C/title%3E%3Cg stroke='none' stroke-width='1' fill='none ' fill-rule='evenodd' fill-opacity='0'%3E%3Cg transform='translate(-249.000000, -126.000000)]' fill='%23FFFFFF'%3E%3Crect x='249' y=' 126' width='1' height='1'%3E%3C/rect%3E%3C/g%3E%3C/g%3E%3C/svg%3E)

Let's talk about the starting point of the decoration mode. As can be seen from the figure, the interface Componentactually already has ConcreteComponentAand ConcreteComponentBTwo implementation classes, but if we want to enhance these two implementation classes, we can use the decoration mode and use specific The decorator to decorate the implementation class to achieve the purpose of enhancement.

Let's briefly explain the decorator from the name. Since it is a decoration, it is often to add small functions , and we can add multiple small functions if we are satisfied. In the simplest way, the proxy mode can achieve functional enhancement, but it is not easy for the proxy to enhance multiple functions. Of course, you can use the multi-layer packaging method of proxy packaging proxy, but in that case the code will be complicated.

First understand some simple concepts. We can see from the figure that all concrete decorators ConcreteDecorator * can be used as Component because they all implement all interfaces in Component. The difference between them and the Component implementation class ConcreteComponent* is that they are just decorators for decoration , that is, even though they look awesome, they are just a layer of skin for decoration in the specific implementation .

Note that Component and Decorator are mixed in various nouns in this passage, don't confuse them.

Let's take a look at an example, first clarify the decoration mode, and then introduce the application of the decoration mode in java io.

Recently, "Happy Lemon" has become popular on the street. We divide the drinks of Happy Lemon into three categories: black tea, green tea, and coffee. On the basis of these three categories, we have added many flavors, such as kumquat lemon black tea, Kumquat lemon pearl green tea, mango black tea, mango green tea, mango pearl black tea, roasted pearl black tea, roasted pearl mango green tea, coconut-flavored germ coffee, caramel cocoa coffee, etc. Each store has a long menu, but look carefully Next, there are not many raw materials, but many combinations can be made. If customers need it, they can also make many drinks that do not appear in the menu.

In this example, black tea, green tea, and coffee are the most basic drinks, and others like kumquat lemon, mango, pearl, coconut, and caramel are all decorative. Of course, in development, we can indeed develop these classes like a store: LemonBlackTea, LemonGreenTea, MangoBlackTea, MangoLemonGreenTea... However, we soon discovered that this is definitely not possible, which will cause us to combine all Possibly, and what if a guest needs a double lemon in their black tea? What about three lemons?

Stop talking nonsense and go to the code.

First, define the beverage abstract base class:

public abstract class Beverage {      // 返回描述      public abstract String getDescription();      // 返回价格      public abstract double cost();}

Then there are three basic beverage implementation classes, black tea, green tea and coffee:

public class BlackTea extends Beverage {      public String getDescription() {        return "红茶";    }      public double cost() {        return 10;    }}public class GreenTea extends Beverage {    public String getDescription() {        return "绿茶";    }      public double cost() {        return 11;    }}...// 咖啡省略

Define the seasoning, which is the base class of the decorator, which must inherit from Beverage:

// 调料public abstract class Condiment extends Beverage {}

Then let's define specific condiments such as lemon and mango, which belong to decorators. There is no doubt that these condiments must inherit the Condiment class:

public class Lemon extends Condiment {
    private Beverage bevarage;
    // 这里很关键,需要传入具体的饮料,如需要传入没有被装饰的红茶或绿茶,
    // 当然也可以传入已经装饰好的芒果绿茶,这样可以做芒果柠檬绿茶
    public Lemon(Beverage bevarage) {
        this.bevarage = bevarage;
    }
    public String getDescription() {
        // 装饰
        return bevarage.getDescription() + ", 加柠檬";
    }
    public double cost() {
        // 装饰
        return beverage.cost() + 2; // 加柠檬需要 2 元
    }
}

public class Mango extends Condiment {
    private Beverage bevarage;
    public Mango(Beverage bevarage) {
        this.bevarage = bevarage;
    }
    public String getDescription() {
        return bevarage.getDescription() + ", 加芒果";
    }
    public double cost() {
        return beverage.cost() + 3; // 加芒果需要 3 元
    }
}
...// 给每一种调料都加一个类

Look at the client call:

public static void main(String[] args) {
    // 首先,我们需要一个基础饮料,红茶、绿茶或咖啡
    Beverage beverage = new GreenTea();
    // 开始装饰
    beverage = new Lemon(beverage); // 先加一份柠檬
    beverage = new Mongo(beverage); // 再加一份芒果

    System.out.println(beverage.getDescription() + " 价格:¥" + beverage.cost());
    //"绿茶, 加柠檬, 加芒果 价格:¥16"
}

If we need Mango -Pearl-Double Lemon-Black Tea :

Beverage beverage = new Mongo(new Pearl(new Lemon(new Lemon(new BlackTea()))));

Is it perverted?

It may be clearer to look at the picture below:

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-hO8ODsC6-1683340130678)(data:image/svg+xml,%3C%3Fxml version='1.0' encoding= 'UTF-8'%3F%3E%3Csvg width='1px' height='1px' viewBox='0 0 1 1' version='1.1' xmlns='http://www.w3.org/2000/svg ' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Ctitle%3E%3C/title%3E%3Cg stroke='none' stroke-width='1' fill='none ' fill-rule='evenodd' fill-opacity='0'%3E%3Cg transform='translate(-249.000000, -126.000000)]' fill='%23FFFFFF'%3E%3Crect x='249' y=' 126' width='1' height='1'%3E%3C/rect%3E%3C/g%3E%3C/g%3E%3C/svg%3E)

At this point, everyone should already know the decoration mode.

Next, let's talk about the decoration mode in java IO. Look at some classes derived from InputStream in the figure below:

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-qL1JnQ7u-1683340130680)(data:image/svg+xml,%3C%3Fxml version='1.0' encoding= 'UTF-8'%3F%3E%3Csvg width='1px' height='1px' viewBox='0 0 1 1' version='1.1' xmlns='http://www.w3.org/2000/svg ' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Ctitle%3E%3C/title%3E%3Cg stroke='none' stroke-width='1' fill='none ' fill-rule='evenodd' fill-opacity='0'%3E%3Cg transform='translate(-249.000000, -126.000000)]' fill='%23FFFFFF'%3E%3Crect x='249' y=' 126' width='1' height='1'%3E%3C/rect%3E%3C/g%3E%3C/g%3E%3C/svg%3E)

We know that InputStream represents the input stream, and the specific input sources can be files (FileInputStream), pipes (PipedInputStream), arrays (ByteArrayInputStream), etc. These are like the black tea and green tea in the milk tea example above, which belong to the basic input stream.

FilterInputStream inherits the key nodes of the decoration mode. Its implementation class is a series of decorators. For example, BufferedInputStream represents decoration with buffering, which makes the input stream have a buffering function. LineNumberInputStream represents decoration with line numbers. When operating You can get the line number, and the decoration of DataInputStream allows us to convert from the input stream to the basic type value in java.

Of course, in java IO, if we use decorators, it is not suitable for interface-oriented programming, such as:

InputStream inputStream = new LineNumberInputStream(new BufferedInputStream(new FileInputStream("")));

As a result, InputStream still does not have the function of reading line numbers, because the method of reading line numbers is defined in the LineNumberInputStream class.

We should use it like this:

DataInputStream is = new DataInputStream(
                              new BufferedInputStream(
                                  new FileInputStream("")));

So, it is still difficult to find pure code that strictly conforms to the design pattern.

facade mode

The facade pattern (also known as the appearance pattern, Facade Pattern) is used in many source codes, such as slf4j, which can be understood as the application of the facade pattern. This is a simple design pattern, let's talk about it directly in the code.

First, we define an interface:

public interface Shape {
   void draw();
}

Define several implementation classes:

public class Circle implements Shape {
    @Override
    public void draw() {
       System.out.println("Circle::draw()");
    }
}

public class Rectangle implements Shape {
    @Override
    public void draw() {
       System.out.println("Rectangle::draw()");
    }
}

Client call:

public static void main(String[] args) {
    // 画一个圆形
      Shape circle = new Circle();
      circle.draw();

      // 画一个长方形
      Shape rectangle = new Rectangle();
      rectangle.draw();
}

The above is the code we often write. If we need to draw a circle, we need to instantiate a circle first. To draw a rectangle, we need to instantiate a rectangle first, and then call the corresponding draw() method.

Next, let's see how to use the facade mode to make client calls more friendly.

Let's define a facade first:

public class ShapeMaker {
   private Shape circle;
   private Shape rectangle;
   private Shape square;

   public ShapeMaker() {
      circle = new Circle();
      rectangle = new Rectangle();
      square = new Square();
   }

  /**
   * 下面定义一堆方法,具体应该调用什么方法,由这个门面来决定
   */

   public void drawCircle(){
      circle.draw();
   }
   public void drawRectangle(){
      rectangle.draw();
   }
   public void drawSquare(){
      square.draw();
   }
}

See how the client calls now:

public static void main(String[] args) {
  ShapeMaker shapeMaker = new ShapeMaker();

  // 客户端调用现在更加清晰了
  shapeMaker.drawCircle();
  shapeMaker.drawRectangle();
  shapeMaker.drawSquare();
}

The advantages of the facade mode are obvious. The client no longer needs to pay attention to which implementation class should be used when instantiating, and can directly call the method provided by the facade, because the method name of the method provided by the facade class is already very friendly to the client.

combination mode

The composite pattern is used to represent data with a hierarchical structure, making our access to individual objects and composite objects consistent.

Just look at an example. Each employee has attributes such as name, department, and salary, as well as a collection of subordinate employees (although the collection may be empty), and subordinate employees have the same structure as themselves, and they also have attributes such as name and department. , while also having their subordinate employee collections.

public class Employee {
   private String name;
   private String dept;
   private int salary;
   private List<Employee> subordinates; // 下属

   public Employee(String name,String dept, int sal) {
      this.name = name;
      this.dept = dept;
      this.salary = sal;
      subordinates = new ArrayList<Employee>();
   }

   public void add(Employee e) {
      subordinates.add(e);
   }

   public void remove(Employee e) {
      subordinates.remove(e);
   }

   public List<Employee> getSubordinates(){
     return subordinates;
   }

   public String toString(){
      return ("Employee :[ Name : " + name + ", dept : " + dept + ", salary :" + salary+" ]");
   }
}

Usually, such classes need to define add(node), remove(node), getChildren() methods.

This is actually the combination mode. I won’t introduce this simple mode too much. I believe readers don’t like to see me write nonsense.

Flyweight mode

English is Flyweight Pattern. I don’t know who translated this word first. I feel that this translation is really difficult to understand. Let’s try to force the connection. Flyweight means lightweight. Flyweight means sharing components separately, that is, reusing generated objects. Of course, this approach is also lightweight.

The easiest way to reuse objects is to use a HashMap to store each newly generated object. Every time an object is needed, first go to the HashMap to see if there is one, if not, generate a new object, and then put this object into the HashMap.

I will not demonstrate this simple code.

Summary of Structural Patterns

Earlier, we talked about proxy mode, adapter mode, bridge mode, decoration mode, facade mode, combination mode and flyweight mode. Can readers clarify these modes separately? When talking about these modes, do you have a clear picture or processing flow in your mind?

Proxy mode is method enhancement. Adapter mode is used to adapt interfaces by packaging chicken into ducks. Bridge mode achieves good decoupling. Decoration mode can be seen from the name, which is suitable for decoration classes or Speaking of enhanced scenarios, the advantage of the facade mode is that the client does not need to care about the instantiation process, just call the required method, the combination mode is used to describe data with a hierarchical structure, and the flyweight mode is for specific scenarios. Cache created objects to improve performance.

behavioral model

The behavioral pattern focuses on the interaction between various classes, and clearly divides responsibilities, making our code clearer.

strategy pattern

The strategy pattern is so common that it is introduced at the front. It's relatively simple, so I won't talk nonsense, just use code to talk about things.

The scenario designed below is that we need to draw a figure. The optional strategy is to draw with a red pen, a green pen, or a blue pen.

First, define a strategy interface:

public interface Strategy {
   public void draw(int radius, int x, int y);
}

Then we define several specific strategies:

public class RedPen implements Strategy {
   @Override
   public void draw(int radius, int x, int y) {
      System.out.println("用红色笔画图,radius:" + radius + ", x:" + x + ", y:" + y);
   }
}
public class GreenPen implements Strategy {
   @Override
   public void draw(int radius, int x, int y) {
      System.out.println("用绿色笔画图,radius:" + radius + ", x:" + x + ", y:" + y);
   }
}
public class BluePen implements Strategy {
   @Override
   public void draw(int radius, int x, int y) {
      System.out.println("用蓝色笔画图,radius:" + radius + ", x:" + x + ", y:" + y);
   }
}

Classes using strategies:

public class Context {
   private Strategy strategy;

   public Context(Strategy strategy){
      this.strategy = strategy;
   }

   public int executeDraw(int radius, int x, int y){
      return strategy.draw(radius, x, y);
   }
}

Client demo:

public static void main(String[] args) {
    Context context = new Context(new BluePen()); // 使用绿色笔来画
      context.executeDraw(10, 0, 0);
}

Put it on a picture so that everyone can see it clearly:

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-x7c89ttA-1683340130681)(data:image/svg+xml,%3C%3Fxml version='1.0' encoding= 'UTF-8'%3F%3E%3Csvg width='1px' height='1px' viewBox='0 0 1 1' version='1.1' xmlns='http://www.w3.org/2000/svg ' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Ctitle%3E%3C/title%3E%3Cg stroke='none' stroke-width='1' fill='none ' fill-rule='evenodd' fill-opacity='0'%3E%3Cg transform='translate(-249.000000, -126.000000)]' fill='%23FFFFFF'%3E%3Crect x='249' y=' 126' width='1' height='1'%3E%3C/rect%3E%3C/g%3E%3C/g%3E%3C/svg%3E)

At this time, do you think of the bridge mode in the structural mode? They are actually very similar. Let me take the picture of the bridge mode for comparison:

[External link image transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the image and upload it directly (img-ORM33z5f-1683340130682) (data:image/svg+xml,%3C%3Fxml version='1.0' encoding= 'UTF-8'%3F%3E%3Csvg width='1px' height='1px' viewBox='0 0 1 1' version='1.1' xmlns='http://www.w3.org/2000/svg ' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Ctitle%3E%3C/title%3E%3Cg stroke='none' stroke-width='1' fill='none ' fill-rule='evenodd' fill-opacity='0'%3E%3Cg transform='translate(-249.000000, -126.000000)]' fill='%23FFFFFF'%3E%3Crect x='249' y=' 126' width='1' height='1'%3E%3C/rect%3E%3C/g%3E%3C/g%3E%3C/svg%3E)

They are very similar, if I ask you, the bridge pattern just adds a layer of abstraction on the left side. The bridge mode is less coupled and has a more complex structure.

Observer pattern

Observer mode is really simple for us. There are nothing more than two operations. Observers subscribe to the topics they care about and notify observers when there are data changes in the topics.

First, a topic needs to be defined, and each topic needs to hold a reference to the observer list, which is used to notify each observer when the data changes:

public class Subject {
    private List<Observer> observers = new ArrayList<Observer>();
    private int state;
    public int getState() {
        return state;
    }
    public void setState(int state) {
        this.state = state;
        // 数据已变更,通知观察者们
        notifyAllObservers();
    }
    // 注册观察者
    public void attach(Observer observer) {
        observers.add(observer);
    }
    // 通知观察者们
    public void notifyAllObservers() {
        for (Observer observer : observers) {
            observer.update();
        }
    }
}

Define the observer interface:

public abstract class Observer {
    protected Subject subject;
    public abstract void update();
}

In fact, if there is only one observer class, the interface does not need to be defined. However, in general scenarios, since the observer mode is used, we just hope that when an event comes out, there will be multiple different classes that need to process the corresponding information. For example, for the successful order modification event, we hope that the class that sends text messages will be notified, the class that sends emails will be notified, and the class that processes logistics information will be notified.

Let's define several specific observer classes:

public class BinaryObserver extends Observer {
    // 在构造方法中进行订阅主题
    public BinaryObserver(Subject subject) {
        this.subject = subject;
        // 通常在构造方法中将 this 发布出去的操作一定要小心
        this.subject.attach(this);
    }
    // 该方法由主题类在数据变更的时候进行调用
    @Override
    public void update() {
        String result = Integer.toBinaryString(subject.getState());
        System.out.println("订阅的数据发生变化,新的数据处理为二进制值为:" + result);
    }
}

public class HexaObserver extends Observer {
    public HexaObserver(Subject subject) {
        this.subject = subject;
        this.subject.attach(this);
    }
    @Override
    public void update() {
        String result = Integer.toHexString(subject.getState()).toUpperCase();
        System.out.println("订阅的数据发生变化,新的数据处理为十六进制值为:" + result);
    }
}

The client is also very simple to use:

public static void main(String[] args) {
    // 先定义一个主题
    Subject subject1 = new Subject();
    // 定义观察者
    new BinaryObserver(subject1);
    new HexaObserver(subject1);

    // 模拟数据变更,这个时候,观察者们的 update 方法将会被调用
    subject.setState(11);
}

output:

订阅的数据发生变化,新的数据处理为二进制值为:1011
订阅的数据发生变化,新的数据处理为十六进制值为:B

Of course, jdk also provides similar support. For details, you can refer to the two classes java.util.Observable and java.util.Observer.

In the actual production process, the observer mode is often implemented with message middleware. If you want to implement the stand-alone observer mode, the author recommends readers to use the EventBus in Guava, which has synchronous and asynchronous implementations. This article mainly introduces the design mode and will not expand it. said.

Also, even the above code will have many variants. You just need to remember the core part, that is, there must be a place to store all the observers, and then when an event occurs, traverse the observers and call them callback function.

chain of responsibility model

The chain of responsibility usually needs to establish a one-way linked list first, and then the caller only needs to call the head node, and then it will automatically flow down. For example, process approval is a good example. As long as the end user submits an application, a chain of responsibility is automatically established according to the content information of the application, and then the transfer can begin.

There is such a scenario that users can receive prizes for participating in an event, but the event needs to be verified by a lot of rules before it can be released. For example, it is first necessary to verify whether the user is a new user, whether there is a limit on the number of participants today, and whether the number of participants in the event is limited. Uh wait. Only after all the set rules are passed can the user be allowed to claim the prize.

If the product gives you this requirement, I think what most people must think at the beginning is to use a List to store all the rules, and then execute each rule with foreach. However, readers, don't worry, let's see what is the difference between the chain of responsibility model and the one we talked about?

First, we need to define the base class of nodes on the process:

public abstract class RuleHandler {
    // 后继节点
    protected RuleHandler successor;

    public abstract void apply(Context context);

    public void setSuccessor(RuleHandler successor) {
        this.successor = successor;
    }

    public RuleHandler getSuccessor() {
        return successor;
    }
}

Next, we need to define each specific node.

Check if the user is a new user:

public class NewUserRuleHandler extends RuleHandler {
    public void apply(Context context) {
        if (context.isNewUser()) {
            // 如果有后继节点的话,传递下去
            if (this.getSuccessor() != null) {
                this.getSuccessor().apply(context);
            }
        } else {
            throw new RuntimeException("该活动仅限新用户参与");
        }
    }
}

Check whether the user's region can participate:

public class LocationRuleHandler extends RuleHandler {
    public void apply(Context context) {
        boolean allowed = activityService.isSupportedLocation(context.getLocation);
        if (allowed) {
            if (this.getSuccessor() != null) {
                this.getSuccessor().apply(context);
            }
        } else {
            throw new RuntimeException("非常抱歉,您所在的地区无法参与本次活动");
        }
    }
}

Check if the prize has been claimed:

public class LimitRuleHandler extends RuleHandler {
    public void apply(Context context) {
        int remainedTimes = activityService.queryRemainedTimes(context); // 查询剩余奖品
        if (remainedTimes > 0) {
            if (this.getSuccessor() != null) {
                this.getSuccessor().apply(userInfo);
            }
        } else {
            throw new RuntimeException("您来得太晚了,奖品被领完了");
        }
    }
}

client:

public static void main(String[] args) {
    RuleHandler newUserHandler = new NewUserRuleHandler();
    RuleHandler locationHandler = new LocationRuleHandler();
    RuleHandler limitHandler = new LimitRuleHandler();

    // 假设本次活动仅校验地区和奖品数量,不校验新老用户
    locationHandler.setSuccessor(limitHandler);

    locationHandler.apply(context);
}

The code is actually very simple, that is, first define a linked list, and then after passing through any node, if this node has a successor node, then pass it on.

As for the similarities and differences between it and the practice of using a List to store the rules that need to be executed, I leave it to the readers to ponder for themselves.

template method pattern

The template method pattern is very commonly used in code with inheritance structures.

Usually there will be an abstract class:

public abstract class AbstractTemplate {
    // 这就是模板方法
    public void templateMethod() {
        init();
        apply(); // 这个是重点
        end(); // 可以作为钩子方法
    }

    protected void init() {
        System.out.println("init 抽象层已经实现,子类也可以选择覆写");
    }

    // 留给子类实现
    protected abstract void apply();

    protected void end() {
    }
}

Three methods are called in the template method, among which apply() is an abstract method, and subclasses must implement it. In fact, several abstract methods in the template method are completely free. We can also set all three methods as abstract methods. Let subclasses do it. In other words, the template method is only responsible for defining what should be done in the first step, what should be done in the second step, and what should be done in the third step. As for how to do it, it is implemented by the subclass.

We write an implementation class:

public class ConcreteTemplate extends AbstractTemplate {
    public void apply() {
        System.out.println("子类实现抽象方法 apply");
    }

    public void end() {
        System.out.println("我们可以把 method3 当做钩子方法来使用,需要的时候覆写就可以了");
    }
}

Client call demo:

public static void main(String[] args) {
    AbstractTemplate t = new ConcreteTemplate();
    // 调用模板方法
    t.templateMethod();
}

The code is actually very simple, basically you can understand it when you see it, the key is to learn to use it in your own code.

state mode

update: 2017-10-19

I won't talk nonsense, let's talk about a simple example. One of the most basic needs of the commodity inventory center is to reduce inventory and replenish inventory. Let's see how to use the state mode to write.

The core is that our focus is no longer what kind of operations the Context should perform, but what operations will be performed in this Context.

Define the state interface:

public interface State {
    public void doAction(Context context);
}

Define the status of destocking:

public class DeductState implements State {

    public void doAction(Context context) {
        System.out.println("商品卖出,准备减库存");
        context.setState(this);

        //... 执行减库存的具体操作
    }

    public String toString() {
        return "Deduct State";
    }
}

Define replenishment status:

public class RevertState implements State {

    public void doAction(Context context) {
        System.out.println("给此商品补库存");
        context.setState(this);

        //... 执行加库存的具体操作
    }

    public String toString() {
        return "Revert State";
    }
}

Context.setState(this) was used earlier, let's see how to define the Context class:

public class Context {
    private State state;
    private String name;
    public Context(String name) {
        this.name = name;
    }

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

Let's take a look at the client call, and everyone will be clear:

public static void main(String[] args) {
        // 我们需要操作的是 iPhone X
        Context context = new Context("iPhone X");

        // 看看怎么进行补库存操作
        State revertState = new RevertState();
        revertState.doAction(context);

        // 同样的,减库存操作也非常简单
        State deductState = new DeductState();
        deductState.doAction(context);

        // 如果需要我们可以获取当前的状态
        // context.getState().toString();
        }

Readers may find that in the above example, if we don't care what state the current context is in, then the Context doesn't need to maintain the state attribute, and the code will be much simpler.

However, the example of commodity inventory is just an example after all, and we still have many examples where we need to know what state the current context is in.

Summary of Behavioral Patterns

The behavioral pattern part introduces the strategy pattern, observer pattern, chain of responsibility pattern, template method pattern, and state pattern. In fact, the classic behavioral pattern also includes memo pattern, command pattern, etc., but their use scenarios are relatively limited, and this article The space is quite large, so I will not introduce it.

Summarize

The purpose of learning design patterns is to make our code more elegant, easy to maintain, and easy to expand. Finishing this article this time made me re-examine the various design patterns, which is quite rewarding for me. I think that the biggest beneficiary of an article is generally the author himself. In order to write an article, he needs to consolidate his knowledge and search for various materials. Moreover, the things he has written are the easiest to remember, which can be regarded as what I give to readers. Suggest it.

Guess you like

Origin blog.csdn.net/u014001523/article/details/130521564