Seven commonly used design patterns

Seven commonly used design patterns: singleton pattern, factory method pattern, abstract factory pattern, proxy pattern, decorator pattern, observer pattern and chain of responsibility pattern.

Design pattern classification

Design patterns are divided into three categories: creative patterns, structural patterns and behavioral patterns according to the purpose of the work.

Creational patterns: singleton pattern , factory method pattern , abstract factory pattern , creator pattern, prototype pattern.

Structural patterns: adapter pattern, proxy pattern , decorator pattern , appearance pattern, bridge pattern, combination pattern, flyweight pattern.

Behavioral patterns: Strategy pattern, Template method pattern, Observer pattern , Iterative sub pattern, Chain of responsibility pattern , Command pattern, Memo pattern, State pattern, Visitor pattern, Mediator pattern, Interpreter pattern.

Seven principles of software design (OOP principles)

Open-closed principle: open to extensions, closed to modifications.
Liskov substitution principle: Do not destroy the inheritance system. If the function of a subclass overridden method changes, it should not affect the meaning of the parent class method.
Dependency inversion principle: Program for interfaces, not implementation.
Single responsibility principle: control the granularity of classes, decouple objects, and improve their cohesion.
Interface isolation principle: establish the dedicated interfaces they need for each class.
Demeter's Law: A class should keep the least knowledge about other objects and reduce coupling.
Principle of synthesis and reuse: Try to use association relationships such as combination or aggregation to implement it first, and then consider using inheritance relationships to implement it.

In fact, the seven principles have only one purpose: to reduce the coupling between objects and increase the reusability, scalability and maintainability of the program.

1. Singleton mode: A mode in which a class has only one instance, and the class can create this instance by itself
        ① A singleton class has only one instance object

        ②The singleton object must be created by the singleton class itself

        ③The singleton class provides a global access point to the singleton.

       ④. Advantages
                : The singleton mode can ensure that there is only one instance in the memory, reducing memory overhead.
                Multiple occupation of resources can be avoided.
                The singleton mode sets a global access point to optimize and share access to resources.
        ⑤. Disadvantages:
                Singleton mode generally has no interface and is difficult to expand.
                The functional code of the singleton mode is usually written in a class. If the functional design is unreasonable, it can easily violate the single responsibility principle.

        Hungry-style singleton: Create a singleton once the class is loaded, ensuring that the singleton already exists before calling the getInstance method. This Hungry-style singleton will cause a waste of space.

public class Hungry {
    private Hungry(){}
    private final static Hungry HUNGRY = new Hungry();
    public static Hungry getInstance(){
        return HUNGRY;
    }
}

        Lazy singleton: In order to avoid wasting memory space, a lazy singleton is used, that is, the singleton object is created when it is used.

public class LazyMan {
    private LazyMan(){};

    public static LazyMan lazyMan;

    public static LazyMan getInstance(){
        if (lazyMan==null){
            lazyMan = new LazyMan();
        }
        return lazyMan;
    }
}

This is the simplest lazy style, but there are big problems. There is no problem with this code under single thread, but there are big problems under multi-threading.

public class LazyMan {
    private LazyMan(){
        System.out.println(Thread.currentThread().getName()+"");
    }

    public static LazyMan lazyMan;

    public static LazyMan getInstance(){
        if (lazyMan==null){
            lazyMan = new LazyMan();
        }
        return lazyMan;
    }

    public static void main(String[] args) {
        for(int i=0;i<10;i++){
            new Thread(()->{
                lazyMan.getInstance();
            }).start();
        }
    }
}

 You will find that the results are different. Therefore, under concurrent conditions, this code is problematic. We need to detect both ends and "lock": synchronized (Singleton.class).

public class LazyMan {
    private LazyMan(){
        System.out.println(Thread.currentThread().getName()+"");
    }

    public static LazyMan lazyMan;

    public static LazyMan getInstance(){
        if (lazyMan==null){        //第一层检查,检查是否有引用指向对象,高并发情况下会有多个线程同时进入
            synchronized (LazyMan.class){    //第一层锁,保证只有一个线程进入
                //双重检查,防止多个线程同时进入第一层检查(因单例模式只允许存在一个对象,故在创建对象之前无引用指向对象,所有线程均可进入第一层检查)
                //当某一线程获得锁创建一个LazyMan对象时,即已有引用指向对象,lazyMan不为空,从而保证只会创建一个对象
                //假设没有第二层检查,那么第一个线程创建完对象释放锁后,后面进入对象也会创建对象,会产生多个对象
                if(lazyMan==null){    //第二层检查
                    //synchronized关键字作用为禁止指令重排,保证返回Singleton对象一定在创建对象后
                    lazyMan = new LazyMan();        //这行代码存在的问题,不能保证原子性
实际上会执行以下内容:
                    //(1)在堆上开辟空间;(2)属性初始化;(3)引用指向对象
                    //假设以上三个内容为三条单独指令,因指令重排可能会导致执行顺序为1->3->2(正常为1->2->3),当单例模式中存在普通变量需要在构造方法中进行初始化操作时,单线程情况下,顺序重排没有影响;但在多线程情况下,假如线程1执行lazyMan = new LazyMan()语句时先1再3,由于系统调度线程2的原因没来得及执行步骤2,但此时已有引用指向对象也就是lazyMan!=null,故线程2在第一次检查时不满足条件直接返回lazyMan,此时lazyMan为null
                    //synchronized关键字可保证lazyMan = new LazyMan()语句执行顺序为123,因其为非原子性依旧可能存在系统调度问题(即执行步骤时被打断),但能确保的是只要lazyMan!=0,就表明一定执行了属性初始化操作;而若在步骤3之前被打断,此时lazyMan依旧为null,其他线程可进入第一层检查向下执行创建对象
                }
            }
        }
        return lazyMan;
    }

    public static void main(String[] args) {
        for(int i=0;i<10;i++){
            new Thread(()->{
                lazyMan.getInstance();
            }).start();
        }
    }
}

 It can be seen that there is only one result. Logically speaking, there is no problem. In fact, it is not. The problem with the above marked line of code is not an atomic operation.

        Static inner class singleton: It is unsafe and has problems. It is unsafe and has problems (the same is true for the modified lazy style singleton).

public class Inner {
    private Inner(){}
    public static Inner getInstance(){
        return InnerClass.INNER;
    }
    public static class InnerClass{
        private static final Inner INNER = new Inner();
    }
}

2. Factory method pattern: Instead of using new to instantiate objects, use factory methods instead. Implementation classes will be selected to create objects for unified management and control. This decouples the caller from our implementation class.

        Simple factory pattern: used to produce any product in the same level architecture (for adding new products, existing code needs to be modified).
In the simple factory pattern, instances of different classes can be returned according to different parameters. The simple factory pattern specifically defines a class to be responsible for creating instances of other classes.

Next create an interface, two implementation classes, a factory, and a test class

//创建手机接口
public interface Phone {
    void name();
}
//创建华为实现类
public class HuaWei implements Phone{
    @Override
    public void name() {
        System.out.println("华为手机");
    }
}
//创建小米实现类
public class XiaoMi implements Phone{
    @Override
    public void name() {
        System.out.println("小米手机");
    }
}
//创建工厂
public class PhoneFactory {
    public static Phone getPhone(String phone){
        if(phone.equals("华为")){
            return new HuaWei();
        }else if(phone.equals("小米")){
            return  new XiaoMi();
        }else {
            return null;
        }
    }
}
//测试类
public class Consumer {
    public static void main(String[] args) {
        Phone p1= PhoneFactory.getPhone("华为");
        Phone p2= PhoneFactory.getPhone("小米");
        p1.name();
        p2.name();
    }
}

Get test results

        Huawei mobile phone
        Xiaomi mobile phone

We successfully completed the creation of the factory by creating a PhoneFactory class. When we create an object, we do not need to create the object directly, but can create a factory, which greatly reduces the coupling of the code. However, static factory mode cannot add data. For example, if we want to add an "Oppo" mobile phone class, you can't do it without directly modifying the PhoneFactory factory code. Therefore, there is the second factory method pattern.

        Factory method pattern: used to produce fixed products in the same hierarchical structure. A factory hierarchical structure can be responsible for the creation of product objects in multiple different product hierarchical structures. (Supports adding any product)

//创建手机接口
public interface Phone {
    void name();
}
//创建华为实现类
public class HuaWei implements Phone{
    @Override
    public void name() {
        System.out.println("华为手机");
    }
}
//创建手机工厂接口
public interface PhoneFactory {
    Phone getPhone();
}
//创建华为工厂
public class HuaWeiFactory implements PhoneFactory{
    @Override
    public Phone getPhone() {
        return new HuaWei();
    }
}
//测试类
public class Consumer {
    public static void main(String[] args) {
        Phone phone = new HuaWeiFactory().getPhone();
        phone.name();
    }
}

Get test results

        Huawei cell phone

We created the mobile phone factory interface PhoneFactory, and then created the Huawei factory HuaWeiFactory to implement the factory, so that objects can be created through HuaWeiFactory. It is very convenient to add new specific factories and product families. For example, if we want to add Xiaomi, we only need to create a Xiaomi factory XiaoMiFactory to implement the mobile phone factory interface PhoneFactory, which reasonably solves the shortcoming of the simple factory model that cannot modify the code. However, in actual use, the simple factory model accounts for the vast majority.

Comparison between simple factory pattern and factory method pattern:

Structural complexity : Simple factory pattern dominates.
Code complexity : Simple factory pattern dominates.
Programming complexity : Simple factory pattern dominates.
Management is complex : the simple factory model prevails.
Therefore, although the simple factory pattern does not conform to the design pattern, the actual use is much greater than the factory method pattern .

3. Abstract Factory Pattern: The Abstract Factory Pattern provides an interface for creating a series of related or interdependent objects without specifying their specific classes (creating other factories around a super factory, which is called a factory of factories)

The main roles of the abstract factory pattern:

        1. Abstract Factory provides an interface for creating products. It contains multiple methods for creating products, newProduct(), and can create multiple products of different levels.
2 Concrete Factory mainly implements multiple abstract methods in the abstract factory to complete the creation of specific products.
3 Abstract product (Product) defines the specifications of the product and describes the main features and functions of the product. The abstract factory pattern has multiple abstract products.
4. The concrete product (ConcreteProduct) implements the interface defined by the abstract product role and is created by the concrete factory. It has a many-to-one relationship with the concrete factory.

//电脑接口
public interface Computer {
    void play();
    void watch();
}
//创建华为电脑对象
public class HuaWeiComputer implements Computer{
    @Override
    public void play() {
        System.out.println("HuaWei's play!");
    }

    @Override
    public void watch() {
        System.out.println("HuaWei's watch!");
    }
}
//手机接口
public interface Phone {
    void send();
    void call();
}
//创建华为手机对象
public class HuaWeiPhone implements Phone{
    @Override
    public void send() {
        System.out.println("HuaWei's send");
    }

    @Override
    public void call() {
        System.out.println("HuaWei's call");
    }
}
//抽象工厂
public interface IProductFactory {
    //生产手机
    Phone phone();
    //生产电脑
    Computer computer();
}
//创建华为工厂
public class HuaWeiFactory implements IProductFactory{
    @Override
    public Phone phone() {
        return new HuaWeiPhone();
    }

    @Override
    public Computer computer() {
        return new HuaWeiComputer();
    }
}
//测试类
public class Consumer {
    public static void main(String[] args) {
        HuaWeiFactory huaWeiFactory = new HuaWeiFactory();
        Phone phone = huaWeiFactory.phone();
        phone.call();
        phone.send();
        Computer computer = huaWeiFactory.computer();
        computer.play();
        computer.watch();
    }
}

Get test results

        HuaWei's call
        HuaWei's send
        HuaWei's play!
        HuaWei's watch!

We have completed the creation of a specific factory by creating an abstract factory, and only need to pass in parameters to instantiate the object. The code of specific products is isolated at the application layer, so there is no need to care about the details of creation to create a series of products together. Create a series of products planned together. However, the abstract factory pattern also has shortcomings. It stipulates all product collections that may be created. It is difficult to expand new products in the product cluster. Products cannot be added, only brands can be added.

4. Proxy mode: For some reasons, it is necessary to provide a proxy for an object to control access to the object. At this time, the access object is not suitable or cannot directly reference the target object, and the proxy object acts as an intermediary between the access object and the target object.

        Static proxy mode:

        Role analysis:           

        1. Abstract role: Generally, interfaces or abstract classes are used to solve the problem.

        2. Real role: the role being represented

        3. Agent role: Act as a real character. After acting as a real character, we will perform some subsidiary operations.

        4. Access role: person who accesses the proxy object

//租房
public interface Rent {
    void rent();
}
//房东
public class Master implements Rent{
    @Override
    public void rent() {
        System.out.println("Master rent");
    }
}
//中介
public class Proxy implements Rent{
    private Master master;

    public Proxy() {
    }

    public Proxy(Master master) {
        this.master = master;
    }

    @Override
    public void rent() {
        see();
        master.rent();
        fare();
    }
    //看房
    public void see(){
        System.out.println("see");
    }
    //收费
    public void fare(){
        System.out.println("fare");
    }
}
//测试类
public class Consumer {
    public static void main(String[] args) {
        Master master = new Master();
        //进行代理
        Proxy proxy = new Proxy(master);
        //不需要通过对象,直接通过代理完成响应业务
        proxy.rent();
    }
}

Get test results

        see
        Master rent
        fare

As can be seen from the above code, we completed the renting by creating an intermediary agent, and there are also subsidiary operations of viewing the house and charging fees. We don't need to use the landlord object, the operation can be completed by using the proxy intermediary. 

Advantages of the agent model: It can make the operation of the real role more pure! There is no need to pay attention to some public businesses, and the public can be handed over to the agent role, realizing the division of labor in the business. When the public business expands, it is convenient for centralized management! Disadvantages of the agency
model : A real role will generate an agent role; the amount of code will double and the development efficiency will become lower. Perhaps, the benefits of the agent model cannot be understood in this way. An example may help us understand it better. For example, if we want to add new services to the original fixed functions, we cannot modify the original code according to the opening and closing principle. However, we can use the proxy mode to add proxies and write new functions while realizing the original functions. When creating objects, we can also use proxies to complete operations.

 Dynamic proxy mode: Although the static proxy mode can solve the opening and closing principle very well, every time there is a real character, an agent will be generated. Doubling the amount of code is too bloated and the development efficiency is low. Therefore, we use the dynamic proxy pattern for design.

//接口
public interface IUserService {
    void add();
    void delete();
    void update();
    void query();

}
//实现类
public class UserServiceImpl implements IUserService {
    @Override
    public void add() {
        System.out.println("add");
    }

    @Override
    public void delete() {
        System.out.println("delete");
    }

    @Override
    public void update() {
        System.out.println("update");
    }

    @Override
    public void query() {
        System.out.println("query");
    }
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//自动生成动态代理类模板
public class ProxyInvocationHandler implements InvocationHandler {
    //被代理接口
    private Object target;
   
    public void setTarget(Object target) {
        this.target = target;
    }
     //得到代理类
    public Object getProxy() {
        return Proxy.newProxyInstance(getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }
    public void log(String s) {
        System.out.println("[debug]:" + s);
    }
    //得到代理类
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log(method.getName());
        Object result = method.invoke(target, args);
        return result;
    }
}
//测试类
public class Consumer {
    public static void main(String[] args) {
        UserServiceImpl userService = new UserServiceImpl();
        ProxyInvocationHandler handler = new ProxyInvocationHandler();
        //设置代理对象
        handler.setTarget(userService);
        //生成代理类
        IUserService proxy = (IUserService)handler.getProxy();
        proxy.add();
        proxy.query();
    }
}

Get test results

        [debug]:add
        add
        [debug]:query
        query

Through testing, we can successfully use the dynamic proxy mode to complete a series of operations. When we want to add subsidiary operations, we only need to add them in the template. Advantages: ① It can make the operation of real characters more pure! No need to pay attention to some public business. ②The public can be handed over to the agent role! The division of labor is realized. ③When public services expand, centralized management is facilitated. ④A dynamic proxy class represents an interface, which is generally the corresponding type of business. ⑤A dynamic proxy class can proxy multiple classes, as long as it implements the same interface!

5. Decorator pattern: Dynamically attach new functions to objects. In terms of expanding object functions, it is more flexible than inheritance. At the same time, the decorator mode also embodies the opening and closing principle.

        Role analysis:

        1. Abstract component (Component) role: Define an abstract interface to standardize objects that are prepared to receive additional responsibilities.
        2. ConcreteComponent role: implement abstract components and add some responsibilities to them through decorative roles.
        3. Abstract Decorator role: Inherits abstract components and contains instances of specific components. The functions of specific components can be extended through its subclasses.
        4. Concrete Decorator role: implements related methods of abstract decoration and adds additional responsibilities to specific component objects.

//定义抽象类
public abstract class Drink {
    public abstract double cost();
}
//定义两个抽象类的实现类
public class Juice extends Drink{
    @Override
    public double cost() {
        System.out.println("juice: "+16);
        return 16;
    }
}
public class Milk extends Drink{
    @Override
    public double cost() {
        System.out.println("milk: "+12);
        return 12;
    }
}
//定义装饰抽象类
public abstract class Decorator extends Drink {
    public abstract double cost();
}
//定义两个装饰具体实现类
public class Chocolate extends Decorator{
    private final static double COST = 4;
    private Drink drink;

    public Chocolate(Drink drink) {
        this.drink = drink;
    }

    @Override
    public double cost() {
        System.out.println("chocolate:"+4);
        return COST+drink.cost();
    }
}
public class Pudding extends Decorator{
    private final static double COST = 5;
    private Drink drink;

    public Pudding(Drink drink) {
        this.drink = drink;
    }

    @Override
    public double cost() {
        System.out.println("pudding:"+5);
        return COST+drink.cost();
    }
}
//测试类
public class Test {
    public static void main(String[] args) {
        Drink milk = new Milk();
        milk = new Pudding(milk);
        milk = new Chocolate(milk);
        System.out.println(milk.cost());
    }
}

Get test results

        chocolate:4
        pudding:5
        milk: 12
        21.0

 

It can be seen that it is very simple to complete the combination of specific components and specific decorations. You can also see that the structure is very concise and clear. The specific components are in their own packages, and the specific decorations are also in their own packages. Whether we want to add specific components or specific accessories, we can add them in their respective packages. There is no need to perform any operations on the existing code. Even if the newly added code has bugs, it will not affect the operation of the original code.

6. Observer pattern: A one-to-many dependency relationship between objects, so that whenever the state of an object changes, its related dependent objects are notified and automatically updated. This model is sometimes called publish-subscribe model or model-view model. It is an object behavioral model.

        Observer pattern has some features:

        1. Reduce the coupling relationship between the target and the observer, and there is an abstract coupling relationship between the two. Complies with the dependency inversion principle.

        2. A trigger mechanism is established between the target and the observer.

//定义观察者接口
public interface Observer {
    void response();
}
//具体化观察者1
public class Observer1 implements Observer{
    @Override
    public void response() {
        System.out.println("Observer1 action");
    }
}
//具体化观察者2
public class Observer2 implements Observer{
    @Override
    public void response() {
        System.out.println("Observer2 action");
    }
}
//抽象目标
public abstract class Subject {
    //创建观察者集合
    protected ArrayList<Observer> array = new ArrayList<Observer>();
    //增加观察者
    public void add(Observer observer){
        array.add(observer);
    }
    //删除观察者
    public void remove(Observer observer){
        array.remove(observer);
    }
    //通知观察者方法
    public abstract void notifyObserver();
}
//具体化目标
public class Subject1 extends Subject{
    @Override
    public void notifyObserver() {
        for (Observer observer :array){
            observer.response();
        }
    }
}
//测试类
public class Test {
    public static void main(String[] args) {
        Subject subject = new Subject1();
        Observer obs1 = new Observer1();
        Observer obs2 = new Observer2();
        subject.add(obs1);
        subject.add(obs2);
        subject.notifyObserver();
    }
}

Get test results:

        Observer1 action
        Observer2 action

Through testing, we can see that we do not need to establish too many connections between the observer and the specific target, which greatly reduces the coupling relationship between the target and the observer. And the structure is also very simple and clear, with observers and targets in their own packages. When we want to add an observer, we only need to add it under the observer package and implement the Observer interface.

7. Chain of responsibility model: A model for processing requests that allows multiple processors to have the opportunity to process the request until one of them is successfully processed. The chain of responsibility pattern strings multiple processors into a chain, and then passes the request along the chain.
        The main role of the chain of responsibility pattern is
        the abstract handler (Handler) role: defines an interface for processing requests, including abstract processing methods and a subsequent connection.
        Concrete Handler role: Implement the processing method of the abstract handler to determine whether the request can be processed. If the request can be processed, process it, otherwise the request will be transferred to its successor.
        Client class (Client) role: Creates a processing chain and submits a request to the specific handler object of the chain head. It does not care about the processing details and request delivery process.
        The advantage of the chain of responsibility model is
                that it reduces the coupling between objects. The handler does not need to know anything about the client, nor does the client know how the handler implements the method.
                Improved system flexibility. When we want to add processors to the entire chain, the price paid is a very small
        disadvantage of the chain of responsibility model,
                which reduces the performance of the system. Compared with a long chain of responsibilities, the processing of requests may involve multiple processing objects
                and there is no guarantee that each request will be processed. Since a request has no clear recipient, there is no guarantee that it will be processed, and the request may not be processed until it reaches the end of the chain.

//抽象处理者
public abstract class Handler {
    private Handler next;
    public void setNext(Handler next) { this.next = next; }
    public Handler getNext() { return next; }

    //处理请求
    public abstract void handleRequest(int info);
}
//具体处理者1
public class Handler1 extends Handler{
    @Override
    public void handleRequest(int info) {
        if (info <10){
            System.out.println("Handler1完成处理");
        }else {
            if (getNext()!=null){
                getNext().handleRequest(info);
            }else {
                System.out.println("没有处理者进行处理");
            }
        }
    }
}
//具体处理者2
public class Handler2 extends Handler{
    @Override
    public void handleRequest(int info) {
        if (info <20&&info>10){
            System.out.println("Handler2完成处理");
        }else {
            if (getNext()!=null){
                getNext().handleRequest(info);
            }else {
                System.out.println("没有处理者进行处理");
            }
        }
    }
}
测试类
public class Test {
    public static void main(String[] args) {
        Handler handler1 = new Handler1();
        Handler handler2 = new Handler2();
        handler1.setNext(handler2);
        handler1.handleRequest(5);
        handler1.handleRequest(15);
        handler1.handleRequest(25);
    }
}

Get test results:

Handler1 has completed processing.
Handler2 has completed processing.
There is no handler for processing.

From the test results, we can see that 5 is handled by Handler1, 15 is handled by Handler2, and 25 is not handled by a handler. The requester does not need to participate in the processing at all. He only needs to submit the data to complete the function processing, and there is no need to worry about which processor performs the processing. When we want to continue adding handlers, we only need to add them again, and it will not affect the previous code.

Guess you like

Origin blog.csdn.net/a154555/article/details/125612512