Structural patterns describe how classes or objects are grouped into larger structures according to some layout. It is divided into class structure pattern and object structure pattern, the former uses inheritance mechanism to organize interfaces and classes, the latter uses composition or aggregation to combine objects.

Because the coupling degree of combinatorial or aggregative relation is lower than that of inheritance relation and meets the “composite reuse principle”, object structural pattern has more flexibility than class structural pattern.

Structural modes are divided into the following 7 types:

  • The proxy pattern
  • Adapter mode
  • Decorator mode
  • The bridge model
  • The appearance model
  • Portfolio model
  • The flyweight pattern

5.1 Proxy Mode

5.1.1 overview

For some reason, a proxy needs to be provided to an object to control access to the object. When the access object is not suitable or cannot directly reference the target object, the proxy object acts as an intermediary between the access object and the target object.

Java proxies can be 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 generated dynamically at Java runtime. There are JDK proxies and CGLib proxies.

5.1.2 structure

The Proxy mode is divided into three roles:

  • Abstract Subject class: Business methods that declare real topics and proxy object implementations through interfaces or abstract classes.
  • The Real Subject class: implements the concrete business in the abstract Subject class, is the clinic object represented by the proxy object, and is the object ultimately referenced.
  • Proxy class: Provides the same interface as a real topic, with internal references to real topics that can access, control, or extend the functionality of real topics.

5.1.3 Static Proxy

We can use an example to get a feel for static proxies

The railway station sells tickets

If you want to buy train tickets, you need to go to the train station to sell tickets, take a bus to the train station, queue up and a series of operations, obviously more troublesome. And the train station is in many places have agent points, we go to the agent points to sell tickets is a lot more convenient. In fact, this example is a typical agent mode, the railway station is the target object, the agent is the object. The class diagram is as follows:

The code is as follows:

public class HelloWorld {
    public static void main(String[] args) {
        ProxyPoint pp = newProxyPoint(); pp.sell(); }}// Ticket interface
interface SellTickets {
    void sell(a);
}

// train station: train station can SellTickets, so you need to implement the SellTickets interface
class TrainStation implements SellTickets {
    @Override
    public void sell(a) {
        System.out.println("Train station tickets."); }}/ / outlets
class ProxyPoint implements SellTickets {
    private TrainStation station = new TrainStation();
    
    @Override
    public void sell(a) {
        System.out.println("The agency collects some service charges."); station.sell(); }}Copy the code

It can be seen from the above code that the test class directly accesses the ProxyPoint class object, that is to say, ProxyPoint acts as the intermediary to access the object and the target object, and the Sell method is also enhanced (the agent charges some service fees).

5.1.4 JDK Dynamic Proxy

Let’s use dynamic proxies to implement the above example, starting with the dynamic proxies provided by the JDK. Java provides a dynamic Proxy class, Proxy, which is not a Proxy object class, but provides a static method to create a Proxy object (newProxyInstance) to get the Proxy object.

The code is as follows:

public class HelloWorld {
    public static void main(String[] args) {
        // Get the proxy object
        ProxyFactory factory = newProxyFactory(); SellTickets proxyObject = factory.getProxyObject(); proxyObject.sell(); }}// Ticket interface
interface SellTickets {
    void sell(a);
}

// The train station has the function of selling tickets, so we need to implement the SellTickets interface
class TrainStation implements SellTickets {
    @Override
    public void sell(a) {
        System.out.println("Train station tickets."); }}// Proxy factory, used to create proxy objects
class ProxyFactory {
    private TrainStation station = new TrainStation();

    public SellTickets getProxyObject(a) {
        // Use Proxy to obtain the Proxy object

        /** * newProxyInstance(); /** * newProxyInstance(); >[] interfaces: the same interfaces implemented by real objects and proxy objects * InvocationHandler h: the call handler for proxy objects */

        SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(
                station.getClass().getClassLoader(),
                new InvocationHandler() {
                    /** * Invoke method parameter description in InvocationHandle: * proxy: proxy object * method: method instance corresponding to the method called on the proxy object * args: proxy object call interface method is the actual parameter passed */
                    
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("Agents charge for some services (JDK dynamic proxy method)");
                        
                        // Execute the real object
                        Object result = method.invoke(station, args);
                        returnresult; }});returnsellTickets; }}Copy the code

5.1.5 CGLIB Dynamic proxy

Again, in the above example, we use the CGLIB proxy implementation.

If the SellTickets interface is not defined, only TrainStation (TrainStation class) is defined. It is clear that JDK proxies are not available because JDK dynamic proxies require interfaces to be defined and proxying interfaces.

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

CGLIB is a third-party package, so we need to import the coordinates of the JAR package:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>
Copy the code

The code is as follows:

public class HelloWorld {
    public static void main(String[] args) {
        // Create the proxy factory object
        ProxyFactory factory = new ProxyFactory();
        
        // Get the proxy objectTrainStation proxyObject = factory.getProxyObject(); proxyObject.sell(); }}// The train station is used to sell tickets
class TrainStation {
    public void sell(a) {
        System.out.println("Train station tickets."); }}// Agent factory
public class ProxyFactory implements MethodInterceptor {
    private TrainStation target = new TrainStation();

    public TrainStation getProxyObject(a) {
        // Create the Enhancer object, a Proxy class similar to JDK dynamic proxies. The next step is to set several parameters
        Enhancer enhancer = new Enhancer();
        
        // Sets the bytecode object of the parent class
        enhancer.setSuperclass(target.getClass());
        
        // Set the callback function
        enhancer.setCallback(this);
        
        // Create a proxy object
        TrainStation obj = (TrainStation) enhancer.create();
        
        return obj;
    }
    
    public TrainStation intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        /** * Intercept method parameter description: * o: proxy object * method: method instance of a method in a real object * args: actual parameter * methodProxy: method instance of a method in a proxy object */
        
        System.out.println("Agent charges some service fee (CGLIB dynamic proxy)");
        
        TrainStation result = (TrainStation) methodProxy.invokeSuper(o, args);
        
        returnresult; }}Copy the code

5.1.6 Comparison of the three agents

JDK proxy and CGLIB proxy

CGLIB uses ASM bytecode generation framework and bytecode technology to generate proxy classes, which is more efficient than Java reflection before JDK1.6. The only thing to note is that CGLIB cannot proxy classes or methods that are declared final because CGLIB’s principle is to dynamically subclass the proxied class.

JDK1.6, JDK1.7, and JDK1.8 gradually optimize the JDK dynamic proxy, JDK proxy efficiency is higher than CGLIB proxy efficiency in the case of fewer calls, only when a large number of calls are made, JDK1.6, JDK1.7 proxy efficiency is lower than JDK1.6, JDK1.7 proxy efficiency. But by JDK1.8, the JDK agent was more efficient than the CGLIB agent. So if there are interfaces that use JDK dynamic proxies, if there are no interfaces that use CGLIB proxies.

Dynamic and static proxies

The biggest advantage of dynamic proxies compared to static proxies is that all methods declared in the interface are moved to a centralized method of the calling handler (InvocationHandler.invoke). In this way, when the number of interface methods is large, we can be flexible and do not need to have each method mediated like static proxies.

If the interface adds a method, all the proxy classes in static proxy mode need to implement the method in addition to all the implementation classes, increasing the responsibility of code maintenance, whereas dynamic proxy does not have this problem.

5.1.7 the pros and cons

Advantages:

  • Proxy mode plays an intermediary role and protects the target object between the client and the target object.
  • Proxy objects extend the functionality of target objects;
  • The code mode can separate the client from the target object and reduce the coupling degree of the system to a certain extent.

Disadvantages:

  • Increased system complexity

5.1.8 Application Scenarios

  • Remote proxy: A local service requests a Remote service over the network. In order to achieve local to remote communication, we need to implement network communication and deal with possible exceptions. For good code design and maintainability, we hide the network communication part and expose the local service as an excuse to access the functionality provided by the remote service without having to go into too much detail about the communication part
  • Firewall proxy: When you configure your browser to use proxy, the Firewall forwards your browser’s requests to the Internet. When the Internet returns a response, the proxy server forwards it to your browser
  • Protect or Access agents: Control Access to an object and give different levels of Access to different users if needed

5.2 Adapter Mode

5.2.1 overview

If you travel to European countries, their sockets on the far left of the picture below are European standards. The plug we use is on the far right of the picture below, so our laptops and mobile phones cannot be directly charged locally. So we need a socket converter, the first side of the converter plugs into the current socket, the second side for us to charge, so that our plug can be used in the current. There are many such examples in life, such as mobile phone charger (which converts 220V into 5V voltage), card reader, etc., is actually used in the adapter mode.

Converting the interface of one class into another that the customer wants makes it possible for classes that would otherwise not work together due to interface incompatibilities to work together.

The adapter pattern is divided into class adapter pattern and object adapter pattern. The former has higher coupling degree than the latter, and requires programmers to understand the internal structure of related components in the existing component library, so the application is relatively less.

5.2.2 structure

Adapter patterns contain the following key roles:

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

5.2.3 Class adapter pattern

A card reader

An existing computer can only read SD cards, but to read TF card content, it is necessary to use the adapter mode, create a card reader, read TF card content out.

Implementation: Define an adapter class to implement the business interface of the current system, while inheriting components that already exist in the existing component library.

The class diagram is as follows:

The code is as follows:

public class HelloWorld {
    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 = newSDAdapterTF(); System.out.println(computer.readSD(adapter)); }}// Interface of the SD card
interface SDCard {
    // Read the SD card function
    String readSD(a);

    // Write the SD card function
    void writeSD(String msg);
}

// SD card implementation class
class SDCardImpl implements SDCard {
    @Override
    public String readSD(a) {
        String msg = "sd card read a msg: hello sd card";
        return msg;
    }

    @Override
    public void writeSD(String msg) {
        System.out.println("sd card write msg: "+ msg); }}/ / computer class
class Computer {
    public String readSD(SDCard sdCard) {
        if (sdCard == null) {
            throw new NullPointerException("sd card null");
        }
        returnsdCard.readSD(); }}// TF card interface
interface TFCard {
    // Read the TF card function
    String readTF(a);

    // Write TF card functions
    void writeTF(String msg);
}

// TF card implementation class
class TFCardImpl implements TFCard {
    @Override
    public String readTF(a) {
        String msg = "sd card read a msg: hello tf card";
        return msg;
    }

    @Override
    public void writeTF(String msg) {
        System.out.println("tf card write msg: "+ msg); }}// Define adapter classes (SD-compatible TF)
class SDAdapterTF extends TFCardImpl implements SDCard {
    @Override
    public String readSD(a) {
        System.out.println("adapter read tf card ");
        return readTF();
    }

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

The class adapter pattern violates the principle of composite reuse. The class adapter is available when the client class has an interface specification and not available otherwise.

5.2.4 Object adapter Mode

Implementation: The object adapter pattern can be used to introduce components already implemented in the existing component library into the adapter, which also implements the business interface of the current system.

The class diagram is as follows:

The code is as follows:

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

public class HelloWorld {
    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 = newSDAdapterTF(tfCard); System.out.println(computer.readSD(adapter)); }}// Interface of the SD card
interface SDCard {
    // Read the SD card function
    String readSD(a);

    // Write the SD card function
    void writeSD(String msg);
}

// SD card implementation class
class SDCardImpl implements SDCard {
    @Override
    public String readSD(a) {
        String msg = "sd card read a msg: hello sd card";
        return msg;
    }

    @Override
    public void writeSD(String msg) {
        System.out.println("sd card write msg: "+ msg); }}/ / computer class
class Computer {
    public String readSD(SDCard sdCard) {
        if (sdCard == null) {
            throw new NullPointerException("sd card null");
        }
        returnsdCard.readSD(); }}// TF card interface
interface TFCard {
    // Read the TF card function
    String readTF(a);

    // Write TF card functions
    void writeTF(String msg);
}

// TF card implementation class
class TFCardImpl implements TFCard {
    @Override
    public String readTF(a) {
        String msg = "sd card read a msg: hello tf card";
        return msg;
    }

    @Override
    public void writeTF(String msg) {
        System.out.println("tf card write msg: "+ msg); }}// Define adapter classes (SD-compatible TF)
class SDAdapterTF implements SDCard {
    private TFCard tfCard;

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

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

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

Note: Another adapter is the interface adapter pattern. When we don’t want to implement all of the methods in an interface, we can create an Abstract class Adapter that implements all of the methods, and we just 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 their interfaces are inconsistent with those of the new system
  • Use a component provided by a third party, but the interface definition of the component is different from the interface definition required by you

5.3 Decorator mode

5.3.1 overview

Fast food restaurants have fried noodles, fried rice these fast food, can be additional eggs, ham, bacon these side dishes, of course, add side dishes need extra money, the price of each side dish is usually not the same, then the total price will be more troublesome.

Definition: a pattern that dynamically adds responsibilities to an object without changing its structure.

5.3.2 structure

Roles in Decorator pattern:

  • Abstract Component Role: Defines an abstract interface to specify objects that are ready to receive additional responsibilities
  • Concrete Component roles: Implement abstract artifacts and add responsibilities to them by decorating roles
  • Decorator role: Inherits or implements an abstract artifact and contains instances of the concrete artifact that can be subclassed to extend its functionality
  • Concrete Decorator role: Implement the relevant methods of abstract Decorator and add additional responsibilities to Concrete component objects

5.3.3 case

The class diagram is as follows:

The code is as follows:

public class HelloWorld {
    public static void main(String[] args) {
        // Order fried rice
        FastFood food = new FriedRice();
        // The price of the cost
        System.out.println(food.getDesc() + "" + food.cost() + "Yuan");

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

        // Order fried rice with eggs
        FastFood food1 = new FriedRice();
        food1 = new Egg(food1);
        // The price of the cost
        System.out.println(food1.getDesc() + "" + food1.cost() + "Yuan");

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

        // Order chow mein with bacon
        FastFood food2 = new FriedNoodles();
        food2 = new Bacon(food2);
        // The price of the cost
        System.out.println(food2.getDesc() + "" + food2.cost() + "Yuan"); }}// Fast food abstract class
abstract class FastFood {
    private float price;
    private String desc;

    public FastFood(a) {}

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

    public float getPrice(a) {
        return price;
    }

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

    public String getDesc(a) {
        return desc;
    }

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

    // Get the price
    public abstract float cost(a);
}

/ / Fried rice
class FriedRice extends FastFood {
    public FriedRice(a) {
        super(10."Fried rice");
    }

    @Override
    public float cost(a) {
        returngetPrice(); }}/ / Fried noodles
class FriedNoodles extends FastFood {
    public FriedNoodles(a) {
        super(12."Fried noodles");
    }

    @Override
    public float cost(a) {
        returngetPrice(); }}/ / ingredients
abstract class Garnish extends FastFood {
    private FastFood fastFood;

    public FastFood getFastFood(a) {
        return fastFood;
    }

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

    public Garnish(FastFood fastFood, float price, String desc) {
        super(price, desc);
        this.fastFood = fastFood; }}// Egg ingredients
class Egg extends Garnish {
    public Egg(FastFood fastFood) {
        super(fastFood, 1."Eggs");
    }

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

    @Override
    public String getDesc(a) {
        return super.getDesc() + getFastFood().getDesc(); }}// Bacon toppings
class Bacon extends Garnish {
    public Bacon(FastFood fastFood) {
        super(fastFood,2."Bacon");
    }

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

    @Override
    public String getDesc(a) {
        return super.getDesc() + getFastFood().getDesc(); }}Copy the code
  • Decorator patterns allow for more flexible extensibility than inheritance, and use more methods to combine different decorator objects to obtain diverse results with different behavior states. Decorator mode is more expansible than inheritance, and perfectly follows the open and closed principle. Inheritance is static additional responsibility, while decorator is dynamic additional responsibility.
  • Decorator and decorator classes can be developed independently and not coupled to each other. Decorator pattern is an inherited alternative pattern that can dynamically extend the functionality of an implementation class.

5.3.4 Application Scenarios

  • When the system cannot be extended by inheritance or inheritance is not conducive to system expansion and maintenance. There are two main types of situations in which inheritance is not possible:
    • The first is the existence of a large number of independent extensions in the system, to support each combination will generate a large number of subclasses, resulting in an explosive growth in the number of subclasses;
    • The second type is because the class definition cannot inherit (such as final classes)
  • Add responsibilities to a single object dynamically and transparently without affecting other objects.
  • When the functional requirements of an object can be dynamically added or removed.

5.3.5 The difference between agent and decorator

Differences between static proxy and decorator patterns:

Similarities:

  • All implement the same business interface as the target class
  • Declare the target object in both classes
  • You can enhance the target method without modifying the target class

Difference:

  • The purpose is different: decorators enhance the target object and static proxies protect and hide the target object
  • Get where the target object is built and where the decorator is passed in from the outside. You can hide the target object by passing in the constructor that static proxies are created inside the proxy class