Design Patterns - day03

5. Structural mode

Structural patterns describe how to organize classes or objects into larger structures in a certain layout. It is divided into class structure pattern and object structure pattern. The former uses inheritance mechanism to organize interfaces and classes, and the latter uses combination or aggregation to combine objects.

Since the combination or aggregation relationship is less coupled than the inheritance relationship and satisfies the "principle of composite reuse", the object structure model has greater flexibility than the class structure model.

Structural patterns are divided into the following seven types:

  • Proxy mode
  • adapter pattern
  • decorator pattern
  • bridge mode
  • appearance mode
  • combination mode
  • Flyweight mode

5.1 Proxy mode

5.1.1 Overview

For some reason it is necessary to provide an object with a proxy to control access to that object. At this time, the access object is not suitable or cannot directly refer to the target object, and the proxy object acts as an intermediary between the access object and the target object.

Proxies in Java are divided into static proxies and dynamic proxies according to the generation time of proxy classes. The static proxy class is generated at compile time, while the dynamic proxy class is dynamically generated at Java runtime. There are two types of dynamic proxy: JDK proxy and CGLib proxy.

5.1.2 Structure

The Proxy mode is divided into three roles:

  • Abstract subject (Subject) class: Declare the business methods implemented by real subjects and proxy objects through interfaces or abstract classes.
  • Real Subject (Real Subject) class: realizes the specific business in the abstract subject, is the real object represented by the proxy object, and is the final object to be referenced.
  • Proxy (Proxy) class: Provides the same interface as the real theme, which contains references to the real theme, which can access, control or extend the functions of the real theme.

5.1.3 Static proxy

Let's take a look at the static proxy through a case.

[Example] selling tickets at a train station

If you want to buy a train ticket, you need to go to the train station to buy the ticket, take the train to the train station, wait in line and wait for a series of operations, which is obviously more troublesome. And the railway station has sales offices in many places, so it is much more convenient for us to go to the sales offices to buy tickets. This example is actually a typical agent model, the train station is the target object, and the sales agency is the agent object. The class diagram is as follows:

code show as below:

//卖票接口
public interface SellTickets {
    void sell();
}

//火车站  火车站具有卖票功能,所以需要实现SellTickets接口
public class TrainStation implements SellTickets {

    public void sell() {
        System.out.println("火车站卖票");
    }
}

//代售点
public class ProxyPoint implements SellTickets {

    private TrainStation station = new TrainStation();

    public void sell() {
        System.out.println("代理点收取一些服务费用");
        station.sell();
    }
}

//测试类
public class Client {
    public static void main(String[] args) {
        ProxyPoint pp = new ProxyPoint();
        pp.sell();
    }
}

It can be seen from the above code that the test class directly accesses the ProxyPoint class object, that is to say, the ProxyPoint acts as an intermediary between the access object and the target object. At the same time, the sell method has also been enhanced (agents charge some service fees).

5.1.4 JDK dynamic proxy

Next, we use dynamic proxies to implement the above case. First, let's talk about the dynamic proxies provided by JDK. Java provides a dynamic proxy class Proxy. Proxy is not the class of the proxy object we mentioned above, but provides a static method (newProxyInstance method) to create a proxy object to obtain the proxy object.

code show as below:

//卖票接口
public interface SellTickets {
    void sell();
}

//火车站  火车站具有卖票功能,所以需要实现SellTickets接口
public class TrainStation implements SellTickets {

    public void sell() {
        System.out.println("火车站卖票");
    }
}

//代理工厂,用来创建代理对象
public class ProxyFactory {

    private TrainStation station = new TrainStation();

    public SellTickets getProxyObject() {
        //使用Proxy获取代理对象
        /*
            newProxyInstance()方法参数说明:
                ClassLoader loader : 类加载器,用于加载代理类,使用真实对象的类加载器即可
                Class<?>[] interfaces : 真实对象所实现的接口,代理模式真实对象和代理对象实现相同的接口
                InvocationHandler h : 代理对象的调用处理程序
         */
        SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(station.getClass().getClassLoader(),
                station.getClass().getInterfaces(),
                new InvocationHandler() {
                    /*
                        InvocationHandler中invoke方法参数说明:
                            proxy : 代理对象
                            method : 对应于在代理对象上调用的接口方法的 Method 实例
                            args : 代理对象调用接口方法时传递的实际参数
                     */
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                        System.out.println("代理点收取一些服务费用(JDK动态代理方式)");
                        //执行真实对象
                        Object result = method.invoke(station, args);
                        return result;
                    }
                });
        return sellTickets;
    }
}

//测试类
public class Client {
    public static void main(String[] args) {
        //获取代理对象
        ProxyFactory factory = new ProxyFactory();

        SellTickets proxyObject = factory.getProxyObject();
        proxyObject.sell();
    }
}

Using dynamic proxy, we think about the following questions:

  • Is ProxyFactory a proxy class?

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

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

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

    • The proxy class ($Proxy0) implements SellTickets. This also confirms what we said before that the real class and the proxy class implement the same interface.
    • The proxy class ($Proxy0) passes the anonymous inner class object we provided to the parent class.
  • What is the execution process of dynamic proxy?

    The following is the key code extracted:

    //程序运行过程中动态生成的代理类
    public final class $Proxy0 extends Proxy implements SellTickets {
        private static Method m3;
    
        public $Proxy0(InvocationHandler invocationHandler) {
            super(invocationHandler);
        }
    
        static {
            m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]);
        }
    
        public final void sell() {
            this.h.invoke(this, m3, null);
        }
    }
    
    //Java提供的动态代理相关类
    public class Proxy implements java.io.Serializable {
        protected InvocationHandler h;
    
        protected Proxy(InvocationHandler h) {
            this.h = h;
        }
    }
    
    //代理工厂类
    public class ProxyFactory {
    
        private TrainStation station = new TrainStation();
    
        public SellTickets getProxyObject() {
            SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(station.getClass().getClassLoader(),
                    station.getClass().getInterfaces(),
                    new InvocationHandler() {
    
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
                            System.out.println("代理点收取一些服务费用(JDK动态代理方式)");
                            Object result = method.invoke(station, args);
                            return result;
                        }
                    });
            return sellTickets;
        }
    }
    //测试访问类
     public class Client {
         public static void main(String[] args) {
         //获取代理对象
         ProxyFactory factory = new ProxyFactory();
         SellTickets proxyObject = factory.getProxyObject();
         proxyObject.sell();
         }
     }

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

code show as below:

//火车站
public class TrainStation {

    public void sell() {
        System.out.println("火车站卖票");
    }
}

//代理工厂
public class ProxyFactory implements MethodInterceptor {

    private TrainStation target = new TrainStation();

    public TrainStation getProxyObject() {
        //创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数
        Enhancer enhancer =new Enhancer();
        //设置父类的字节码对象
        enhancer.setSuperclass(target.getClass());
        //设置回调函数
        enhancer.setCallback(this);
        //创建代理对象
        TrainStation obj = (TrainStation) enhancer.create();
        return obj;
    }

    /*
        intercept方法参数说明:
            o : 代理对象
            method : 真实对象中的方法的Method实例
            args : 实际参数
            methodProxy :代理对象中的方法的method实例
     */
    public TrainStation intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("代理点收取一些服务费用(CGLIB动态代理方式)");
        TrainStation result = (TrainStation) methodProxy.invokeSuper(o, args);
        return result;
    }
}

//测试类
public class Client {
    public static void main(String[] args) {
        //创建代理工厂对象
        ProxyFactory factory = new ProxyFactory();
        //获取代理对象
        TrainStation proxyObject = factory.getProxyObject();

        proxyObject.sell();
    }
}

5.1.6 Comparison of three agents

5.1.5 CGLIB Dynamic Proxy

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

If the SellTickets interface is not defined, only TrainStation (train station class) is defined. Obviously, the JDK proxy cannot be used, because the JDK dynamic proxy requires that the interface must be defined to proxy the interface.

CGLIB is a powerful, high-performance code generation package. It provides proxies for classes that do not implement interfaces, and provides a good complement to JDK's dynamic proxies.

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

The execution process is as follows:

In the test class, the sell() method is called through the proxy object. According to the characteristics of polymorphism, the sell() method in the proxy class ($Proxy0) is executed, and the InvocationHandler is called in the sell() method in the proxy class ($Proxy0). The invoke method of the sub-implementation class object of the interface The invoke method executes the sell() method in the class (TrainStation) to which the real object belongs through reflection

  • jdk proxy and CGLIB proxy

    Use CGLib to implement dynamic proxy. The bottom layer of CGLib adopts ASM bytecode generation framework, and bytecode technology is used to generate proxy classes. Before JDK1.6, it is more efficient than using Java reflection. The only thing to note is that CGLib cannot proxy a class or method declared as final, because the principle of CGLib is to dynamically generate subclasses of the proxy class.

    After JDK1.6, JDK1.7, and JDK1.8 gradually optimized JDK dynamic proxy, the efficiency of JDK proxy is higher than that of CGLib proxy when the number of calls is small. Only when a large number of calls are made, JDK1.6 and JDK1.7 is a little less efficient than CGLib proxy, but when it comes to JDK1.8, JDK proxy is more efficient than CGLib proxy. So if there is an interface use JDK dynamic proxy, if there is no interface use CGLIB proxy.

  • Dynamic Proxy and Static Proxy

    Compared with static proxy, the biggest advantage of dynamic proxy is that all methods declared in the interface are transferred to a centralized method of invocation handler (InvocationHandler.invoke). In this way, when there are a large number of interface methods, we can handle them flexibly, without needing to transfer each method like a static proxy.

    If the interface adds a method, in addition to all implementation classes needing to implement this method in the static proxy mode, all proxy classes also need to implement this method. Increased the complexity of code maintenance. This problem does not occur with dynamic proxies

5.1.7 Advantages and disadvantages

advantage:

  • The proxy mode plays an intermediary role between the client and the target object and protects the target object;
  • The proxy object can extend the functionality of the target object;
  • The proxy mode can separate the client from the target object, reducing the coupling of the system to a certain extent;

shortcoming:

  • Increased system complexity;

5.1.8 Usage Scenarios

  • Remote proxy

    Local services request remote services over the network. In order to achieve local-to-remote communication, we need to implement network communication and handle possible exceptions. For good code design and maintainability, we hide the network communication part and only expose an interface to the local service, through which the functions provided by the remote service can be accessed without paying too much attention to the details of the communication part.

  • Firewall proxy

    When you configure your browser to use the proxy function, the firewall forwards your browser's request to the Internet; when the Internet returns a response, the proxy server forwards it to your browser.

  • Protect or Access agent

    Control access to an object and, if desired, provide different levels of access to different users.

5.2 Adapter mode

5.2.1 Overview

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

definition:

Convert the interface of a class into another interface that the client wants, so that those classes that could not work together due to incompatible interfaces can work together.

​ Adapter mode is divided into class adapter mode and object adapter mode. The former has a higher degree of coupling between classes than the latter, and requires programmers to understand the internal structure of related components in the existing component library, so there are relatively few applications.

5.2.2 Structure

Adapter pattern (Adapter) contains the following main roles:

  • Target (Target) interface: the interface expected by the current system business, which can be an abstract class or an interface.
  • Adaptee class: It is the component interface in the existing component library to be accessed and adapted.
  • Adapter (Adapter) class: It is a converter that converts the adapter interface into the target interface by inheriting or referencing the adapter object, allowing customers to access the adapter in the format of the target interface.

5.2.3 Class Adapter Pattern

Implementation method: define an adapter class to realize the business interface of the current system, and at the same time inherit the existing components in the existing component library.

[Example] card reader

An existing computer can only read the SD card, but if you want to read the content in the TF card, you need to use the adapter mode. Create a card reader to read the contents of the TF card.

The class diagram is as follows:

 

code show as below:

//SD卡的接口
public interface SDCard {
    //读取SD卡方法
    String readSD();
    //写入SD卡功能
    void writeSD(String msg);
}

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

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

//电脑类
public class Computer {

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

//TF卡接口
public interface TFCard {
    //读取TF卡方法
    String readTF();
    //写入TF卡功能
    void writeTF(String msg);
}

//TF卡实现类
public class TFCardImpl implements TFCard {

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

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

//定义适配器类(SD兼容TF)
public class SDAdapterTF extends TFCardImpl implements SDCard {

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

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

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

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

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

The Class Adapter pattern violates the principle of compositional reuse. A class adapter is available if the client class has an interface specification, and not otherwise.

5.2.4 Object Adapter Pattern

Implementation method: The object adapter mode can be used to introduce the components already implemented in the existing component library into the adapter class, and this class realizes the business interface of the current system at the same time.

[Example] card reader

We use the object adapter pattern to rewrite the case of the card reader. The class diagram is as follows:

 

code show as below:

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

//创建适配器对象(SD兼容TF)
public class SDAdapterTF  implements SDCard {

    private TFCard tfCard;

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

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

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

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

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

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

Note: Another adapter pattern is the interface adapter pattern. When you don't want to implement all the methods in an interface, you can create an abstract class Adapter to implement all the methods. At this time, we only need to inherit the abstract class.

5.2.5 Application scenarios

  • The previously developed system has classes that meet the functional requirements of the new system, but its interface is inconsistent with the interface of the new system.
  • Use the components provided by the third party, but the component interface definition is different from the interface definition required by yourself.

5.2.6 JDK source code analysis

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

InputStreamReader inherits from Reader in the java.io package, and implements the abstract unimplemented methods in it. like:

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

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

For sd (StreamDecoder class object) in the above code, in Sun's JDK implementation, the actual method implementation is the call encapsulation of the method with the same name of the sun.nio.cs.StreamDecoder class. The class structure diagram is as follows:

As can be seen from the figure above:

  • InputStreamReader is an encapsulation of StreamDecoder which also implements Reader.
  • StreamDecoder is not part of the Java SE API, but its own implementation given by Sun JDK. But we know that they encapsulate the byte stream class (InputStream) in the construction method, and use this class to perform decoding conversion between byte stream and character stream.

in conclusion:

​ From the superficial point of view, InputStreamReader converts the InputStream byte stream class to the Reader character stream. It can be seen from the implementation class relationship structure in Sun JDK above that the design and implementation of StreamDecoder actually adopts the adapter mode.

5.3 Decorator pattern

5.3.1 Overview

Let's start with an example of a fast food restaurant.

Fast food restaurants offer fast food such as fried noodles and fried rice. Additional side dishes such as eggs, ham, and bacon can be added. Of course, additional side dishes need to be charged. The price of each side dish is usually different, so calculating the total price will be more troublesome.

 

 

Problems with using inheritance:

  • poor scalability

    If we want to add another ingredient (ham sausage), we will find that we need to define a subclass for FriedRice and FriedNoodles respectively. If you want to add a fast food category (fried rice noodles), you need to define more subcategories.

  • too many subclasses

definition:

​ Refers to the mode of dynamically adding some responsibilities (that is, adding additional functions) to the object without changing the structure of the existing object.

5.3.2 Structure

Roles in Decorator mode:

  • Abstract component (Component) role: define an abstract interface to standardize the object ready to receive additional responsibilities.
  • Concrete Component role: implement an abstract component and add some responsibilities to it by decorating the role.
  • Abstract decoration (Decorator) role: Inherit or implement abstract components, and contain instances of concrete components, and can extend the functions of concrete components through its subclasses.
  • Concrete Decorator (ConcreteDecorator) role: implements related methods of abstract decoration, and adds additional responsibilities to concrete component objects.

5.3.3 Case

We use the decorator pattern to improve the fast food restaurant case and experience the essence of the decorator pattern.

The class diagram is as follows:

 

code show as below:

//快餐接口
public abstract class FastFood {
    private float price;
    private String desc;

    public FastFood() {
    }

    public FastFood(float price, String desc) {
        this.price = price;
        this.desc = desc;
    }

    public void setPrice(float price) {
        this.price = price;
    }

    public float getPrice() {
        return price;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    public abstract float cost();  //获取价格
}

//炒饭
public class FriedRice extends FastFood {

    public FriedRice() {
        super(10, "炒饭");
    }

    public float cost() {
        return getPrice();
    }
}

//炒面
public class FriedNoodles extends FastFood {

    public FriedNoodles() {
        super(12, "炒面");
    }

    public float cost() {
        return getPrice();
    }
}

//配料类
public abstract class Garnish extends FastFood {

    private FastFood fastFood;

    public FastFood getFastFood() {
        return fastFood;
    }

    public void setFastFood(FastFood fastFood) {
        this.fastFood = fastFood;
    }

    public Garnish(FastFood fastFood, float price, String desc) {
        super(price,desc);
        this.fastFood = fastFood;
    }
}

//鸡蛋配料
public class Egg extends Garnish {

    public Egg(FastFood fastFood) {
        super(fastFood,1,"鸡蛋");
    }

    public float cost() {
        return getPrice() + getFastFood().getPrice();
    }

    @Override
    public String getDesc() {
        return super.getDesc() + getFastFood().getDesc();
    }
}

//培根配料
public class Bacon extends Garnish {

    public Bacon(FastFood fastFood) {

        super(fastFood,2,"培根");
    }

    @Override
    public float cost() {
        return getPrice() + getFastFood().getPrice();
    }

    @Override
    public String getDesc() {
        return super.getDesc() + getFastFood().getDesc();
    }
}

//测试类
public class Client {
    public static void main(String[] args) {
        //点一份炒饭
        FastFood food = new FriedRice();
        //花费的价格
        System.out.println(food.getDesc() + " " + food.cost() + "元");

        System.out.println("========");
        //点一份加鸡蛋的炒饭
        FastFood food1 = new FriedRice();

        food1 = new Egg(food1);
        //花费的价格
        System.out.println(food1.getDesc() + " " + food1.cost() + "元");

        System.out.println("========");
        //点一份加培根的炒面
        FastFood food2 = new FriedNoodles();
        food2 = new Bacon(food2);
        //花费的价格
        System.out.println(food2.getDesc() + " " + food2.cost() + "元");
    }
}

 

benefit:

  • Decorator mode can bring more flexible extension functions than inheritance, and is more convenient to use. You can obtain diverse results with different behavior states by combining different decorator objects. The decorator pattern is more extensible than inheritance, and it perfectly follows the principle of opening and closing. Inheritance is a static additional responsibility, and the decorator is a dynamic additional responsibility.

  • The decoration class and the decorated class can develop independently without coupling with each other. The decoration mode is an alternative mode of inheritance. The decoration mode can dynamically extend the function of an implementation class.

5.3.4 Usage Scenarios

  • When the system cannot be expanded by inheritance or inheritance is not conducive to system expansion and maintenance.

    There are two main categories of situations where inheritance cannot be used:

    • The first category is that there are a large number of independent extensions in the system. To support each combination, a large number of subcategories will be generated, causing the number of subcategories to grow explosively;
    • The second category is because class definitions cannot be inherited (such as final classes)
  • Add responsibilities to individual objects dynamically and transparently without affecting other objects.

  • When the functional requirements of the object can be added dynamically, and can also be revoked dynamically.

5.3.5 JDK source code analysis

The wrapper class in the IO stream uses the decorator pattern. BufferedInputStream, BufferedOutputStream, BufferedReader, BufferedWriter.

Let's take BufferedWriter as an example, let's see how to use BufferedWriter

public class Demo {
    public static void main(String[] args) throws Exception{
        //创建BufferedWriter对象
        //创建FileWriter对象
        FileWriter fw = new FileWriter("C:\\Users\\Think\\Desktop\\a.txt");
        BufferedWriter bw = new BufferedWriter(fw);

        //写数据
        bw.write("hello Buffered");

        bw.close();
    }
}

It really feels like a decorator pattern to use, let's look at their structure:

![](file://D:\work\data-ja va design pattern (illustration + framework source code analysis + actual combat) \Java design pattern data day03\notes\img\decorator mode-jdk source code.png?msec= 1680692529880)

summary:

​ BufferedWriter uses the decorator pattern to enhance the Writer sub-implementation class, adding a buffer and improving the efficiency of writing data.

5.3.6 The difference between proxies and decorators

The difference between static proxy and decorator mode:

  • Same point:
    • must implement the same business interface as the target class
    • Declare the target object in both classes
    • can enhance the target method without modifying the target class
  • difference:
    • The purpose is different. The decorator is to enhance the target object. The static proxy is to protect and hide the target object.
    • The place where the target object is constructed is different. The decorator is passed in from the outside, and the static proxy can be passed through the construction method. The static proxy is created inside the proxy class to hide the target object.

5.4 Bridge mode

5.4.1 Overview

Now there is a need to create different graphics, and each graphics may have different colors. We can use inheritance to design the relationship of classes:

 

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

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

definition:

Separating the abstraction from the implementation so that they can vary independently. It is implemented by replacing the inheritance relationship with the composition relationship, thereby reducing the coupling degree of the two variable dimensions of abstraction and implementation.

5.4.2 Structure

Bridge mode contains the following main roles:

  • Abstraction role: Defines an abstract class and contains a reference to an implementing object.
  • Extended abstraction (Refined Abstraction) role: it is a subclass of the abstraction role, implements the business method in the parent class, and calls the business method in the realized role through the composition relationship.
  • Implementor role: Define the interface of the implementor role, which can be called by the extended abstract role.
  • Concrete Implementor role: Gives the concrete implementation of the implementor role interface.

5.4.3 Case

[Example] Video player

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

The class diagram is as follows:

 

code show as below:

//视频文件
public interface VideoFile {
    void decode(String fileName);
}

//avi文件
public class AVIFile implements VideoFile {
    public void decode(String fileName) {
        System.out.println("avi视频文件:"+ fileName);
    }
}

//rmvb文件
public class REVBBFile implements VideoFile {

    public void decode(String fileName) {
        System.out.println("rmvb文件:" + fileName);
    }
}

//操作系统版本
public abstract class OperatingSystemVersion {

    protected VideoFile videoFile;

    public OperatingSystemVersion(VideoFile videoFile) {
        this.videoFile = videoFile;
    }

    public abstract void play(String fileName);
}

//Windows版本
public class Windows extends OperatingSystem {

    public Windows(VideoFile videoFile) {
        super(videoFile);
    }

    public void play(String fileName) {
        videoFile.decode(fileName);
    }
}

//mac版本
public class Mac extends OperatingSystemVersion {

    public Mac(VideoFile videoFile) {
        super(videoFile);
    }

    public void play(String fileName) {
        videoFile.decode(fileName);
    }
}

//测试类
public class Client {
    public static void main(String[] args) {
        OperatingSystem os = new Windows(new AVIFile());
        os.play("战狼3");
    }
}

 

benefit:

  • The bridging mode improves the scalability of the system. Any expansion of one of the two change dimensions does not require modification of the original system.

    For example: if there is still a video file type wmv, we only need to define another class to implement the VideoFile interface, and other classes do not need to be changed.

  • Implementation details are transparent to customers

5.4.4 Usage Scenarios

  • When a class has two independently changing dimensions, and both dimensions need to be extended.
  • When a system does not want to use inheritance or when the number of system classes increases dramatically due to multi-level inheritance.
  • When a system needs to add more flexibility between the abstract and concrete roles of components. Avoid establishing static inheritance links between the two levels, and they can establish an association relationship at the abstract level through the bridge mode.

5.5 Appearance mode

5.5.1 Overview

Some people may have traded in stocks, but in fact, most of them don’t know much about it. It is easy to lose money in stocks without sufficient knowledge of securities. When you first start trading in stocks, you will definitely think that if you have a knowledgeable helper Well, in fact, the fund is a good helper. There are many funds in Alipay. It gathers the scattered funds of investors, manages them by professional managers, and invests in stocks, bonds, foreign exchange and other fields. Fund investment The income of the token belongs to the holder, and the management agency charges a certain percentage of custody management fees.

definition:

Also known as the facade pattern, it is a pattern that makes these subsystems easier to access by providing a consistent interface for multiple complex subsystems. This mode has a unified interface to the outside world, and the external application program does not need to care about the specific details of the internal subsystem, which will greatly reduce the complexity of the application program and improve the maintainability of the program.

​ Facade mode is a typical application of "Dimit's Law"

 

5.5.2 Structure

The Facade pattern has the following main roles:

  • Facade role: Provide a common interface for multiple subsystems.
  • Subsystem (Sub System) role: implements some functions of the system, and customers can access it through the appearance role.

5.5.3 Case

【Example】Smart home appliance control

Xiao Ming's grandfather is 60 years old and lives alone at home: every time he needs to turn on the lights, turn on the TV, and turn on the air conditioner; turn off the lights, turn off the TV, and turn off the air conditioner when he sleeps; it is more troublesome to operate. So Xiao Ming bought a smart speaker for his grandfather, who can directly control the opening and closing of these smart home appliances through voice. The class diagram is as follows:

 

code show as below:

//灯类
public class Light {
    public void on() {
        System.out.println("打开了灯....");
    }

    public void off() {
        System.out.println("关闭了灯....");
    }
}

//电视类
public class TV {
    public void on() {
        System.out.println("打开了电视....");
    }

    public void off() {
        System.out.println("关闭了电视....");
    }
}

//控制类
public class AirCondition {
    public void on() {
        System.out.println("打开了空调....");
    }

    public void off() {
        System.out.println("关闭了空调....");
    }
}

//智能音箱
public class SmartAppliancesFacade {

    private Light light;
    private TV tv;
    private AirCondition airCondition;

    public SmartAppliancesFacade() {
        light = new Light();
        tv = new TV();
        airCondition = new AirCondition();
    }

    public void say(String message) {
        if(message.contains("打开")) {
            on();
        } else if(message.contains("关闭")) {
            off();
        } else {
            System.out.println("我还听不懂你说的!!!");
        }
    }

    //起床后一键开电器
    private void on() {
        System.out.println("起床了");
        light.on();
        tv.on();
        airCondition.on();
    }

    //睡觉一键关电器
    private void off() {
        System.out.println("睡觉了");
        light.off();
        tv.off();
        airCondition.off();
    }
}

//测试类
public class Client {
    public static void main(String[] args) {
        //创建外观对象
        SmartAppliancesFacade facade = new SmartAppliancesFacade();
        //客户端直接与外观对象进行交互
        facade.say("打开家电");
        facade.say("关闭家电");
    }
}

 

benefit:

  • The degree of coupling between the subsystem and the client is reduced, so that the change of the subsystem will not affect the client class that calls it.
  • The subsystem components are shielded from the client, reducing the number of objects handled by the client and making the subsystem easier to use.

shortcoming:

  • It does not conform to the principle of opening and closing, and it is very troublesome to modify

5.5.4 Usage Scenarios

  • When building hierarchical systems, using the facade pattern to define entry points for each layer in a subsystem simplifies dependencies between subsystems.
  • When a complex system has many subsystems, the appearance mode can design a simple interface for the system to be accessed by the outside world.
  • When there is a large connection between the client and multiple subsystems, the introduction of the facade pattern can separate them, thereby improving the independence and portability of the subsystems.

5.5.5 Source Code Analysis

When using tomcat as the web container, when receiving the request sent by the browser, tomcat will encapsulate the request information into a ServletRequest object, as shown in Figure 1 below. But everyone thinks that ServletRequest is an interface, and it also has a sub-interface HttpServletRequest, and we know that the request object must be a sub-implementation class object of the HttpServletRequest object. Which class object is it? By outputting the request object, we will find that it is an object of a class named RequestFacade.

 

The RequestFacade class uses the facade pattern. Look at the structure diagram first:

 

Why use facade mode here?

​ Define the RequestFacade class, implement ServletRequest respectively, and define the private member variable Request at the same time, and the implementation of the method calls the implementation of Request. Then, convert the RequestFacade to ServletRequest and pass it to the service method of the servlet, so that even if it is converted to RequestFacade in the servlet, the method in the private member variable object cannot be accessed. It not only uses Request, but also prevents its methods from being unreasonably accessed.

Guess you like

Origin blog.csdn.net/fgwynagi/article/details/129963685