Software Design and Architecture - Structural Patterns

If you are interested in learning more about it, please visit my personal website: Pupil Space

Structural Patterns describe how to organize classes or objects into larger structures in a certain layout. Just like building blocks, complex and more powerful structures can be formed through the combination of simple building blocks

insert image description here

Structural patterns can be divided into class structural patterns and object structural patterns

  • The class structure pattern cares about the combination of classes. Multiple classes can be combined into a larger system. In the class structure pattern, there are generally only inheritance relationships and implementation relationships.
  • The object structure model cares about the combination of classes and objects, through the association relationship, the instance object of another class is defined in one class, and then its method is called through the object. According to the "principle of composite reuse", try to use the association relationship to replace the inheritance relationship in the system, so most of the structural patterns are object structural patterns

Structural patterns are divided into the following seven types:

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

One: Proxy mode

Sometimes the client cannot directly manipulate an object, but must interact with that object, such as the following two situations:

  • If the object is a large picture, it will take a long time to display, then you need to make a picture Proxy to replace the real picture
  • If the object is on a remote server, directly operating the object may be slow due to network speed, then we can use Proxy to replace that object first

How to deal with this change? How to provide a mechanism to enable unimpeded communication between two objects that were originally difficult to interact with? How to keep the structure of the system from changing easily as the requirements change? This is the proxy mode.

Proxy mode: Provide a proxy for other objects to control access to this object.

The agent mode is divided into three roles:

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

Agents in Java are divided into different types according to the generation time of agent classes:

  • Static proxy: Static proxy proxy class is generated at compile time
  • Dynamic proxy: The dynamic proxy proxy class is dynamically generated at Java runtime. There are two types of dynamic agents:
    • JDK proxy
    • CGLib Proxy

1.1: static proxy

The following is an example of ticket sales at a railway station:

public class ProxyDemo1 {
    
    
    public static void main(String[] args) {
    
    
        ProxyPoint1 proxyPoint = new ProxyPoint1();
        proxyPoint.sell();
    }
}

// 售票接口
interface SellTickets1 {
    
    
    void sell();
}

// 火车站
class TrainStation1 implements SellTickets1 {
    
    
    @Override
    public void sell() {
    
    
        System.out.println("火车站卖票");
    }
}

// 代售点卖票
class ProxyPoint1 implements SellTickets1 {
    
    
    // 声明火车站类对象
    private TrainStation1 trainStation = new TrainStation1();

    @Override
    public void sell() {
    
    
        System.out.println("收取服务费");
        trainStation.sell();
    }
}

It can be seen from the above code that the main class (acting as 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 is also enhanced (the service fee is charged by the agency point).

1.2: Dynamic proxy

1.2.1: JDK Proxy

Java provides a dynamic proxy class Proxy, which provides a static method (newProxyInstance method) to create a proxy object to obtain a proxy object.

The code for improving the ticket sales at the railway station is as follows:

public class ProxyDemo2 {
    
    
    public static void main(String[] args) {
    
    
        ProxyFactory2 proxyFactory = new ProxyFactory2();
        SellTickets2 proxyObject = proxyFactory.getProxyObject();
        proxyObject.sell();
    }
}

// 售票接口
interface SellTickets2 {
    
    
    void sell();
}

// 火车站
class TrainStation2 implements SellTickets2 {
    
    
    @Override
    public void sell() {
    
    
        System.out.println("火车站卖票");
    }
}

// 代理工厂,用于创建代理对象
class ProxyFactory2 {
    
    
    private TrainStation2 station = new TrainStation2();

    public SellTickets2 getProxyObject() {
    
    
        return (SellTickets2)Proxy.newProxyInstance(
                station.getClass().getClassLoader(),
                station.getClass().getInterfaces(),
                (proxy, method, args) -> {
    
    
                    /*
                     * proxy:与Proxy.newProxyInstance返回值为同一对象
                     * method:对接口中的方法进行封装的method对象
                     * args:调用方法的实际参数
                     */
                    System.out.println("收取服务费");
                    return method.invoke(station, args);
                }
        );
    }
}

1.2.2: CGLIB Proxy

If the SellTickets interface is not defined, only TrainStation (the 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:

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

The code for improving the ticket sales at the railway station is as follows:

public class ProxyDemo3 {
    
    
    public static void main(String[] args) {
    
    
        ProxyFactory3 proxyFactory3 = new ProxyFactory3();
        TrainStation3 proxyObject = proxyFactory3.getProxyObject();
        proxyObject.sell();
    }
}

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

// 代理对象工厂
class ProxyFactory3 implements MethodInterceptor {
    
    
    private TrainStation3 station = new TrainStation3();

    public TrainStation3 getProxyObject() {
    
    
        // 创建Enhancer对象,类似于JDK代理中的Proxy类
        Enhancer enhancer = new Enhancer();
        // 设置父类的字节码对象
        enhancer.setSuperClass(TrainStation3.class);
        // 设置回调函数
        enhancer.setCallback(this);
        // 创建代理对象
        return (proxyObject)enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects. MethodProxy methodProxy) {
    
    
        System.out.println("收取服务费");
        return method.invoke(station, objects);
    }
}

1.3: Summary

Comparison of three agents:

  • JDK proxy and CGLIB proxy: Use CGLIB to implement dynamic proxy. The bottom layer of CGLIB adopts the ASM bytecode generation framework, and uses bytecode technology 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 classes or methods declared as final, because the principle of CGLIB is to dynamically generate subclasses of the proxied 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 processor (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.

Advantages of proxy mode:

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

Disadvantages of proxy mode:

  • increases the complexity of the system

scenes to be used:

  • Remote Proxy: The local service requests the remote service through 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.
  • Virtual Proxy: Cache object information when expensive objects need to be created
  • Protection Proxy: Controls access to the original object. Different levels of access can be given to different users if desired.
  • Smart Reference Proxy (Smart Reference Proxy): When an object is referenced, it provides some additional operations, such as recording the traffic and times of visits, etc.
  • 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.

Two: Adapter mode

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 220x to 5x voltage), card readers, etc., are actually using the adapter mode.

insert image description here

Adapter Pattern: Convert the interface of a class into another interface that the customer wants, so that those classes that could not work together due to interface incompatibility can work together.

Applications:

  • Use an existing class whose interface does not meet the requirements
  • Create a reusable class that can work with other unrelated classes or unforeseen classes (that is, classes whose interfaces may not necessarily be compatible)
  • Use some existing subclasses, but it is not possible to subclass to match the respective interfaces. Object adapter can adapt its parent class interface

insert image description here

The Adapter pattern contains the following main roles:

  • Target interface: the interface expected by the current system business, which can be an abstract class or an interface
  • Adapter class: it is the component interface in the existing component library to be accessed and adapted
  • 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

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.

2.1: Class Adapter

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.

For example, an existing computer can only read the SD card, but if you want to read the contents of the TF card, you need to use the adapter mode. Create a card reader to read the contents of the TF card. code show as below:

public class AdapterDemo1 {
    
    
    public static void main(String[] args) {
    
    
        Computer computer = new Computer();
        String msg = computer.readSD(new SDAdapterTF());
        System.out.println(msg);
    }
}

interface TFCard {
    
    
    String readTF();

    void writeTF(String msg);
}

// 具体TF卡
class TFCardImpl implements TFCard {
    
    
    @Override
    public String readTF() {
    
    
        return "hello TFCard";
    }

    @Override
    public void writeTF(String msg) {
    
    
        System.out.println("TFCard write msg:" + msg);
    }
}

// 目标接口
interface SDCard {
    
    
    String readSD();

    void writeSD(String msg);
}

// 具体SD卡
class SDCardImpl implements SDCard {
    
    

    @Override
    public String readSD() {
    
    
        return "hello SDCard";
    }

    @Override
    public void writeSD(String msg) {
    
    
        System.out.println("SDCard write msg:" + msg);
    }
}

// 计算机类
class Computer {
    
    
    // 从SD卡中读取数据
    public String readSD(SDCard sdCard) {
    
    
        return sdCard.readSD();
    }
}

// 适配器类
class SDAdapterTF extends TFCardImpl implements SDCard {
    
    
    @Override
    public String readSD() {
    
    
        return readTF();
    }

    @Override
    public void writeSD(String msg) {
    
    
        writeTF(msg);
    }
}

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.

2.2: Object Adapter

Realization method: The object adapter mode can introduce the implemented components in the existing component library into the adapter class, which simultaneously realizes the business interface of the current system.

To improve the above card reader case, the code is as follows:

public class AdapterDemo2 {
    
    
    public static void main(String[] args) {
    
    
        Computer2 computer = new Computer2();
        String msg = computer.readSD(new SDAdapterTF2(new TFCardImpl2()));
        System.out.println(msg);
    }
}

interface TFCard2 {
    
    
    String readTF();

    void writeTF(String msg);
}

// 具体TF卡
class TFCardImpl2 implements TFCard2 {
    
    
    @Override
    public String readTF() {
    
    
        return "hello TFCard";
    }

    @Override
    public void writeTF(String msg) {
    
    
        System.out.println("TFCard write msg:" + msg);
    }
}

// 目标接口
interface SDCard2 {
    
    
    String readSD();

    void writeSD(String msg);
}

// 具体SD卡
class SDCardImpl2 implements SDCard2 {
    
    

    @Override
    public String readSD() {
    
    
        return "hello SDCard";
    }

    @Override
    public void writeSD(String msg) {
    
    
        System.out.println("SDCard write msg:" + msg);
    }
}

// 计算机类
class Computer2 {
    
    
    // 从SD卡中读取数据
    public String readSD(SDCard2 sdCard) {
    
    
        return sdCard.readSD();
    }
}

// 适配器类
class SDAdapterTF2 implements SDCard2 {
    
    
    // 声明适配者类
    private final TFCard2 tfCard;

    public SDAdapterTF2(TFCard2 tfCard) {
    
    
        this.tfCard = tfCard;
    }

    @Override
    public String readSD() {
    
    
        return tfCard.readTF();
    }

    @Override
    public void writeSD(String msg) {
    
    
        tfCard.writeTF(msg);
    }
}

Three: Decorator mode

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.

The Decorator Pattern (Decorator Pattern) is a pattern that dynamically adds some responsibilities (that is, adds its additional functions) to the object without changing the object structure.

Roles in the Decorator Pattern

  • Abstract component role: define an abstract interface to standardize objects that are ready to receive additional responsibilities
  • Concrete component roles: implement abstract components and add some responsibilities to them through decoration roles
  • Abstract decoration role: inherit or implement abstract components, and contain instances of concrete components, and can extend the functions of concrete components through its subclasses
  • Concrete decoration role: implement related methods of abstract decoration, and add additional responsibilities to concrete component objects

The sample code is as follows:

public class DecoratorDemo1 {
    
    
    public static void main(String[] args) {
    
    
        // 点一份炒饭
        FastFood food = new FriedRice();
        System.out.println(food.getDesc() + "的价格:" + food.cost());

        // 再加一份鸡蛋
        food = new Egg(food);
        System.out.println(food.getDesc() + "的价格:" + food.cost());

        // 再加一份培根
        food = new Bacon(food);
        System.out.println(food.getDesc() + "的价格:" + food.cost());
    }
}

abstract class FastFood {
    
    
    private float price;
    private String desc;

    public FastFood() {
    
    }

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

    public float getPrice() {
    
    
        return price;
    }

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

    public String getDesc() {
    
    
        return desc;
    }

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

    public abstract float cost();
}

// 炒饭
class FriedRice extends FastFood {
    
    
    public FriedRice() {
    
    
        super(10, "炒饭");
    }

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

// 炒面
class FriedNoodles extends FastFood {
    
    
    public FriedNoodles() {
    
    
        super(12, "炒面");
    }

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

// 装饰类
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;
    }
}

// 配料类(鸡蛋)
class Egg extends Garnish {
    
    

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

    @Override
    public float cost() {
    
    
        // 计算价格
        return getPrice() + getFastFood().cost();
    }

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

// 配料类(培根)
class Bacon extends Garnish {
    
    

    public Bacon(FastFood fastFood) {
    
    
        super(fastFood, 2, "培根");
    }

    @Override
    public float cost() {
    
    
        // 计算价格
        return getPrice() + getFastFood().cost();
    }

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

Benefits of the decorator pattern:

  • The decorator pattern can bring more flexible extension functions than inheritance, and is more convenient to use. You can obtain diverse results with different behavioral 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.

scenes to be used:

  • It is used when the system cannot be extended 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.

Four: Bridge mode

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:
insert image description here

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 of Bridge mode: Separate abstraction from 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.

Bridge mode consists of 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 interface of the implementor role.

The following is an example. 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.

public class BridgeDemo {
    
    
    public static void main(String[] args) {
    
    
        // 创建Mac系统对象
        OperatingSystem system = new Mac(new AviFile());
        system.play("三体");
    }
}

// 视频文件接口
interface VideoFile {
    
    
    // 解码内容
    void decode(String fileName);
}

// Avi格式视频文件类
class AviFile implements VideoFile {
    
    
    @Override
    public void decode(String fileName) {
    
    
        System.out.println("avi视频文件:" + fileName);
    }
}

// Rmvb格式视频文件类
class RmvbFile implements VideoFile {
    
    
    @Override
    public void decode(String fileName) {
    
    
        System.out.println("Rmvb视频文件:" + fileName);
    }
}

// 抽象操作系统类
abstract class OperatingSystem {
    
    
    // 声明videoFile变量
    protected VideoFile videoFile;

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

    public abstract void play(String fileName);
}

// Windows操作系统
class Windows extends OperatingSystem {
    
    
    public Windows(VideoFile videoFile) {
    
    
        super(videoFile);
    }

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

// Mac操作系统
class Mac extends OperatingSystem {
    
    
    public Mac(VideoFile videoFile) {
    
    
        super(videoFile);
    }

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

Advantages of bridge mode:

  • The scalability of the system is improved, and one of the two changing dimensions can be arbitrarily expanded without modifying 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

scenes to be used:

  • 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.

Five: appearance mode

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.

Facade pattern, also known as facade pattern, 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. Appearance mode is a typical application of Demeter's law.

insert image description here

The Facade pattern mainly includes the following main roles:

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

The following is a sample question: Xiao Ming’s grandfather is 60 years old and lives alone at home: he needs to turn on the lights, TV, and air conditioner every time; 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.

code show as below:

public class FacadeDemo {
    
    
    public static void main(String[] args) {
    
    
        SmartApplicationFacade facade = new SmartApplicationFacade();
        facade.say("打开家电");
        System.out.println("-----------------");
        facade.say("关闭家电");
    }
}

// 家具——灯
class Light {
    
    
    public void on() {
    
    
        System.out.println("打开灯。。。");
    }

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

// 家具——电视
class TV {
    
    
    public void on() {
    
    
        System.out.println("打开电视。。。");
    }

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

// 外观角色——智能音箱
class SmartApplicationFacade {
    
    
    private Light light;
    private TV tv;

    public SmartApplicationFacade() {
    
    
        light = new Light();
        tv = new TV();
    }

    private void on() {
    
    
        light.on();
        tv.on();
    }

    private void off() {
    
    
        light.off();
        light.off();
    }

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

Advantages of facade mode:

  • 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.

Disadvantages of facade mode:

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

scenes to be used:

  • 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.

Six: Combination mode

In the figure below, we can regard it as a file system. For such a structure, we call it a tree structure. In the tree structure, the entire tree can be traversed by calling a certain method. When we find a leaf node, we can perform related operations on the leaf node. This tree can be understood as a large container, which contains many member objects, and these member objects can be container objects or leaf objects. However, due to the difference in function between container objects and leaf objects, we must distinguish between container objects and leaf objects during use, but this will bring unnecessary trouble to customers. As a customer, it always hopes to be able to Treat container objects and leaf objects consistently.

insert image description here

Composite mode, also known as part-whole mode, is used to treat a group of similar objects as a single object. The Composite pattern composes objects according to a tree structure, used to represent part and whole hierarchies. This type of design pattern is a structural pattern, which creates a tree structure of groups of objects.

Combination mode mainly includes three roles:

  • Abstract root node (Component): defines the common methods and properties of objects at all levels of the system, and can pre-define some default behaviors and properties.
  • Composite: Define the behavior of a branch node, store child nodes, and combine branch nodes and leaf nodes to form a tree structure.
  • Leaf node (Leaf): Leaf node object, there is no branch under it, and it is the smallest unit of system hierarchy traversal.

The following is a case: As shown in the figure below, when we visit some other management systems, we can often see similar menus. A menu can contain menu items (menu items refer to menu items that no longer contain other content), and can also contain menus with other menu items, so it is appropriate to use the combination mode to describe the menu. Our requirement is for a menu. Print out all the menus it contains! and the names of the menu items.
insert image description here

code show as below:

public class CombinationDemo {
    
    
    public static void main(String[] args) {
    
    
        MenuComponent menu00 = new Menu("系统管理", 0);
        MenuComponent menu10 = new Menu("菜单管理", 1);
        MenuComponent menu11 = new Menu("权限配置", 1);
        MenuComponent menu12 = new Menu("角色管理", 1);
        menu00.add(menu10);
        menu00.add(menu11);
        menu00.add(menu12);

        menu10.add(new MenuItem("页面访问", 2));
        menu10.add(new MenuItem("展开菜单", 2));
        menu10.add(new MenuItem("编辑菜单", 2));
        menu10.add(new MenuItem("删除菜单", 2));
        menu10.add(new MenuItem("新增菜单", 2));

        menu11.add(new MenuItem("页面访问", 2));
        menu11.add(new MenuItem("保存提交", 2));

        menu12.add(new MenuItem("页面访问", 2));
        menu12.add(new MenuItem("新增角色", 2));
        menu12.add(new MenuItem("修改角色", 2));

        menu00.print();
    }
}

// 菜单组件:属于抽象根节点
abstract class MenuComponent {
    
    
    // 菜单组件的名称
    protected String name;
    // 菜单组件的层级
    protected int level;

    // 添加子菜单
    public void add(MenuComponent menuComponent) {
    
    
        throw new UnsupportedOperationException();
    }

    // 移除子菜单
    public void remove(MenuComponent menuComponent) {
    
    
        throw new UnsupportedOperationException();
    }

    // 获取指定的子菜单
    public MenuComponent getChild(int index) {
    
    
        throw new UnsupportedOperationException();
    }

    // 获取菜单或者菜单项的名称
    public String getName() {
    
    
        return name;
    }

    // 打印菜单名称的方法(包含子菜单和子菜单项)
    public abstract void print();
}

// 菜单类:属于树枝节点
class Menu extends MenuComponent {
    
    
    // 菜单可以有多个子菜单或者子菜单项
    private final List<MenuComponent> menuComponentList = new ArrayList<>();

    public Menu(String name, int level) {
    
    
        this.name = name;
        this.level = level;
    }

    @Override
    public void add(MenuComponent menuComponent) {
    
    
        menuComponentList.add(menuComponent);
    }

    @Override
    public void remove(MenuComponent menuComponent) {
    
    
        menuComponentList.remove(menuComponent);
    }

    @Override
    public MenuComponent getChild(int index) {
    
    
        return menuComponentList.get(index);
    }

    @Override
    public void print() {
    
    
        for (int i = 0; i < level; i++) {
    
    
            System.out.print("--");
        }

        // 打印菜单名称
        System.out.println(name);

        // 打印子菜单或者子菜单项名称
        for (MenuComponent component : menuComponentList) {
    
    
            component.print();
        }
    }
}

// 菜单项类:属于叶子节点
class MenuItem extends MenuComponent {
    
    
    public MenuItem(String name, int level) {
    
    
        this.name = name;
        this.level = level;
    }

    @Override
    public void print() {
    
    
        for (int i = 0; i < level; i++) {
    
    
            System.out.print("--");
        }

        System.out.println(name);
    }
}

When using the combination mode, according to the definition form of the abstract component class, we can divide the combination mode into two forms: transparent combination mode and safe combination mode.

  • Transparent Composite Mode
    • All the methods used to manage member objects are declared in the abstract root node role. For example, in the example, Menucomponent declares add, remove, and getChildmethods. The advantage of this is to ensure that all component classes have the same interface. The transparent composite mode is also the standard form of composite mode.
    • The disadvantage is that it is not safe enough, because leaf objects and container objects are essentially different. Leaf objects cannot have objects at the next level, that is, they cannot contain member objects, so it is meaningless to provide methods such as , and so on add(). remove()There will be no errors during the compilation phase, but errors may occur if these methods are called during the runtime phase (if no corresponding error handling code is provided)
  • Security Composite Mode
    • No methods for managing member objects are declared in the abstract component role, but these methods are declared and implemented in the branch node Menu class.
    • The disadvantage is that it is not transparent enough, because the leaf component and the container component have different methods, and those methods used to manage member objects in the container component are not defined in the abstract component class, so the client cannot completely program against the abstraction, and must be treated differently Leaf components and container components.

Advantages of the composite pattern:

  • Composite mode can clearly define hierarchical complex objects, expressing all or part of the object's hierarchy, which allows the client to ignore the differences in hierarchy, and facilitates the control of the entire hierarchy.
  • The client can use a composite structure or a single object in it consistently, regardless of whether it is dealing with a single object or the entire composite structure, which simplifies the client code.
  • It is very convenient to add new branch nodes and leaf nodes in the combination mode without any modification to the existing class library, which conforms to the "opening and closing principle".
  • Combination mode provides a flexible solution for the object-oriented realization of tree structure. Through the recursive combination of leaf nodes and branch nodes, a complex tree structure can be formed, but the control of the tree structure is very simple.

Usage scenario: The combination mode is born in response to the tree structure, so the usage scenario of the combination mode is where the tree structure appears. For example: file directory display, multi-level directory presentation and other tree structure data operations.

Seven: Flyweight mode

Flyweight mode: Use sharing technology to effectively support the reuse of a large number of fine-grained objects. It greatly reduces the number of objects that need to be created by sharing existing objects, and avoids the overhead of a large number of similar objects, thereby improving the utilization of system resources.

The Flyweight mode has the following two states. The implementation of the Flyweight mode is to distinguish the two states in the application and externalize the external state.

  • Internal state, a shareable part that does not change as the environment changes.
  • External state refers to the non-shareable part that changes as the environment changes.

Flyweight mode mainly has the following roles:

  • Abstract flyweight role (Flyweight): usually an interface or an abstract class, which declares the public methods of the specific flyweight class in the abstract flyweight class. These methods can provide the internal data (internal state) of the flyweight object to the outside world, and at the same time External data (external state) can also be set via these methods.
  • Concrete Flyweight role: It implements the abstract flyweight class, called a flyweight object; it provides storage space for the internal state in the concrete flyweight class. Usually we can combine the singleton mode to design specific flyweight classes, and provide unique flyweight objects for each specific flyweight class.
  • Unsharable Flyweight role: Not all subclasses of the abstract flyweight class need to be shared, and subclasses that cannot be shared can be designed as non-shared concrete flyweight classes; when a non-shared concrete flyweight class is required Objects can be created directly by instantiation.
  • Flyweight Factory role: responsible for creating and managing flyweight roles. When a customer object requests a flyweight object, the flyweight factory checks whether there is a flyweight object that meets the requirements in the system, and if it exists, it will be provided to the client; if it does not exist, a new flyweight object will be created.

The following is an example question: The picture below is a block in the well-known Tetris. If in the Tetris game, each different block is an instance object, these objects will take up a lot of memory space. Let’s use Share The meta-pattern is implemented.

insert image description here

code show as below:

public class FlyweightDemo {
    
    
    public static void main(String[] args) {
    
    
        BoxFactory instance = BoxFactory.getInstance();
        instance.getShape("I").display("灰色");
        instance.getShape("L").display("红色");
        instance.getShape("O").display("绿色");
    }
}

// 抽象享元角色
abstract class AbstractBox {
    
    
    // 获取图形的方法
    public abstract String getShape();

    // 显示图形及颜色
    public void display(String color) {
    
    
        System.out.println("方块形状:" + getShape() + ",颜色:" + color);
    }
}

// 具体享元——I图形类
class IBox extends AbstractBox {
    
    
    @Override
    public String getShape() {
    
    
        return "I";
    }
}

// 具体享元——L图形类
class LBox extends AbstractBox {
    
    
    @Override
    public String getShape() {
    
    
        return "L";
    }
}

// 具体享元——O图形类
class OBox extends AbstractBox {
    
    
    @Override
    public String getShape() {
    
    
        return "O";
    }
}

// 工厂类:将该类设置为单例
class BoxFactory {
    
    
    private HashMap<String, AbstractBox> map;
    private static BoxFactory factory = new BoxFactory();

    // 在构造方法中进行初始化操作
    private BoxFactory() {
    
    
        map = new HashMap<>();
        map.put("I", new IBox());
        map.put("L", new LBox());
        map.put("O", new OBox());
    }

    // 获取实例
    public static BoxFactory getInstance() {
    
    
        return factory;
    }

    // 根据名称获取图形对象
    public AbstractBox getShape(String name) {
    
    
        return map.get(name);
    }
}

Advantages of flyweight mode:

  • Greatly reduce the number of similar or identical objects in memory, save system resources and improve system performance
  • The external state in Flyweight mode is relatively independent and does not affect the internal state

Disadvantages of flyweight mode:

  • In order to make the object shareable, it is necessary to externalize part of the state of the flyweight object, separate the internal state and the external state, and complicate the program logic

scenes to be used:

  • A system has a large number of identical or similar objects, resulting in a large amount of memory consumption.
  • Most of the state of an object can be externalized, and these external states can be passed into the object.
  • When using the Flyweight mode, it is necessary to maintain a Flyweight pool for storing the Flyweight objects, which consumes certain system resources. Therefore, it is worth using the Flyweight mode when the Flyweight objects need to be reused many times.

Guess you like

Origin blog.csdn.net/tongkongyu/article/details/127703517