Structural model

1. Structural mode

Structural patterns describe how classes or objects can be arranged into larger structures. It is divided into class structured pattern and object structured pattern. The former uses inheritance mechanism to organize interfaces and classes, and the latter uses composition or aggregation to combine objects.

Because the coupling degree of composite relationship or aggregation relationship is lower than that of inheritance relationship and meets the "composite Reuse Principle", the object structured pattern has more flexibility than the class structured pattern.

Structural modes are divided into the following 7 types:

  • proxy pattern
  • Adapter mode
  • Decorator mode
  • Bridging mode
  • Appearance mode
  • Combination mode
  • Sharing element mode

1.1 agent mode

1.1.1 general

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

Agents in Java can be divided into static agents and dynamic agents according to the generation time of agent classes. Static proxy classes are generated at compile time, while dynamic proxy classes are generated dynamically at Java runtime. Dynamic agents include JDK agent and CGLib agent.

1.1.2 structure

The Proxy mode is divided into three roles:

  • Abstract Subject class: business methods implemented by declaring real subjects and proxy objects through interfaces or abstract classes.
  • Real Subject class: it implements the specific business in the abstract subject. It is the real object represented by the proxy object and the object to be referenced finally.
  • Proxy class: it provides the same interface as the real topic. It contains references to the real topic. It can access, control or extend the functions of the real topic.

1.1.3 static agent

Let's experience the static agent through a case.

Train station ticket selling

If you want to buy a train ticket, you need to go to the railway station to buy a ticket, take a bus to the railway station, queue up and a series of operations, which is obviously more troublesome. The railway station has consignment points in many places. It's much more convenient for us to buy tickets at the consignment point. This example is actually a typical agency model. The railway station is the target object and the consignment point is the agent object.

The code is as follows:

//Ticket selling interface
public interface SellTickets {
    void sell();
}

//The railway station has the function of selling tickets, so the SellTickets interface needs to be implemented
public class TrainStation implements SellTickets {

    public void sell() {
        System.out.println("Train station ticket");
    }
}

//Consignment point
public class ProxyPoint implements SellTickets {

    private TrainStation station = new TrainStation();

    public void sell() {
        System.out.println("The agency charges some service fees");
        station.sell();
    }
}

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

From the above code, we can see that the test class directly accesses the ProxyPoint class object, that is, ProxyPoint acts as the intermediary between the access object and the target object. The sell method is also enhanced (the proxy point charges some service fees).

1.1.4 JDK dynamic agent

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

The code is as follows:

//Ticket selling interface, proxy object
public interface SellTickets {
    void sell();
}

//The railway station has the function of selling tickets, so the SellTickets interface needs to be implemented
public class TrainStation implements SellTickets {

    public void sell() {
        System.out.println("Train station ticket");
    }
}

//Proxy factory, used to create proxy objects
public class ProxyFactory {

    private TrainStation station = new TrainStation();

    public SellTickets getProxyObject() {
        //Get Proxy object using Proxy
        /*
            newProxyInstance()Method parameter description:
                ClassLoader loader :  Class loader, which is used to load proxy classes. You can use the class loader of real objects
                Class<?>[] interfaces :  The interface implemented by the real object, the proxy mode, and the real object and the proxy object implement the same interface
                InvocationHandler h :  Call handler for proxy object
         */
        SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(station.getClass().getClassLoader(),
                station.getClass().getInterfaces(),
                // new Class[]{SellTickets.class}
                new InvocationHandler() {
                    /*
                        InvocationHandler Parameter description of invoke method in:
                            proxy :  Proxy object
                            method :  Method instance corresponding to the interface method called on the proxy object
                            args :  The actual parameters passed when the proxy object calls the interface method
                     */
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                        System.out.println("The agency charges some service fees(JDK Dynamic agent mode)");
                        //Execute real objects   
                        Object result = method.invoke(station, args);
                        return result;
                    }
                });
        return sellTickets;
    }
}

//Test class
public class Client {
    public static void main(String[] args) {
        //Get proxy object
        ProxyFactory factory = new ProxyFactory();
        
        SellTickets proxyObject = factory.getProxyObject();
        proxyObject.sell();
    }
}

Using dynamic agents, we consider 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 process of the program. Check the structure of the proxy class through Alibaba's open source Java diagnostic tool (Arthas):

    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 confirms that the real class and proxy class we mentioned earlier implement the same interface.
    • The proxy class ($Proxy0) passed the anonymous inner class object we provided to the parent class.
  • The execution process of dynamic agent is as follows:

  1. Call the sell() method through the proxy object in the test class
  2. According to the polymorphism, the sell() method in the proxy class ($Proxy0) is executed
  3. The sell() method in the proxy class ($Proxy0) calls the invoke method of the subclass object of the InvocationHandler interface
  4. The invoke method executes the sell() method in the class to which the real object belongs (TrainStation) through reflection

1.1.5 CGLIB dynamic agent

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

If the SellTickets interface is not defined, only trainstation (railway station class) is defined. Obviously, JDK proxy cannot be used, because JDK dynamic proxy requires that interfaces must be defined and proxy interfaces.

CGLIB is a powerful and high-performance code generation package. It provides a proxy for classes that do not implement interfaces, and provides a good supplement to the dynamic proxy of JDK.

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

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

The code is as follows:

//train station
public class TrainStation {

    public void sell() {
        System.out.println("Train station ticket");
    }
}

//Agent factory
public class ProxyFactory implements MethodInterceptor {

    private TrainStation target = new TrainStation();

    public TrainStation getProxyObject() {
        //Create an Enhancer object, which is similar to the Proxy class of JDK dynamic Proxy. The next step is to set several parameters
        Enhancer enhancer =new Enhancer();
        //Set bytecode object of parent class
        enhancer.setSuperclass(target.getClass());
        //Set callback function
        enhancer.setCallback(this);
        //Create proxy object
        TrainStation obj = (TrainStation) enhancer.create();
        return obj;
    }

    /*
        intercept Method parameter description:
            o :  Proxy object
            method :  Method instances of methods in real objects
            args :  Actual parameters
            methodProxy : Method instance of the method in the proxy object
     */
    public TrainStation intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("The agency charges some service fees(CGLIB Dynamic agent mode)");
        TrainStation result = (TrainStation) methodProxy.invokeSuper(o, args);
        return result;
    }
}

//Test class
public class Client {
    public static void main(String[] args) {
        //Create proxy factory object
        ProxyFactory factory = new ProxyFactory();
        //Get proxy object
        TrainStation proxyObject = factory.getProxyObject();

        proxyObject.sell();
    }
}

1.1.6 comparison of three agents

  • jdk agent and CGLIB agent

    CGLib is used to realize dynamic proxy. The bottom layer of CGLib adopts ASM bytecode generation framework and bytecode technology to generate proxy classes in jdk1 Before 6, it was more efficient than using Java reflection. The only thing to note is that CGLib cannot delegate classes or methods declared as final, because the principle of CGLib is to dynamically generate subclasses of the proxied class.

    At jdk1 6,JDK1.7,JDK1.8 after gradually optimizing the JDK dynamic agent, the efficiency of JDK agent is higher than that of cglib agent under the condition of less calls. Only when a large number of calls are made, jdk1 6 and jdk1 7 is a little less efficient than cglib agent, but to jdk1 8, the efficiency of JDK agent is higher than that of cglib agent. Therefore, if there is an interface, use JDK dynamic proxy, and if there is no interface, use cglib proxy.

  • Dynamic agent and static agent

    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 the calling processor (InvocationHandler.invoke). In this way, when there are a large number of interface methods, we can handle them flexibly without transferring each method like a static agent.

    If a method is added to the interface, all proxy classes need to implement this method in addition to all implementation classes in the static proxy mode. It increases the complexity of code maintenance. Dynamic agents do not have this problem

1.1.7 advantages and disadvantages

advantage:

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

Disadvantages:

  • It increases the complexity of the system;

1.1.8 usage scenarios

  • Remote agent

    Local services request remote services over the network. In order to realize local to remote communication, we need to realize network communication and deal with possible exceptions. For good code design and maintainability, we hide the network communication part and expose only one interface to the local service. Through this interface, we can access the functions provided by the remote service 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 will transfer 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. If necessary, different users can be provided with different levels of permissions.

2.2 adapter mode

2.2.1 general

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

definition:

​ Convert the interface of a class into another interface desired by the customer, so that those classes that cannot work together due to incompatible interfaces can work together.

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

2.2.2 structure

The Adapter pattern contains the following main roles:

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

Type 2.2.3 adapter mode

Implementation method: define an adapter class to implement the business interface of the current system and inherit the existing components in the existing component library.

Card reader

An existing computer can only read the SD card, and 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.

The code is as follows:

//SD card interface
public interface SDCard {
    //Method of reading SD card
    String readSD();
    //Write SD card function
    void writeSD(String msg);
}

//SD card implementation class
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);
    }
}

//Computer
public class Computer {

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

//TF card interface
public interface TFCard {
    //Method of reading TF Card
    String readTF();
    //Write TF card function
    void writeTF(String msg);
}

//TF Card implementation class
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);
    }
}

//Define adapter class (SD compatible 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);
    }
}

//Test class
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 composite reuse. Class adapter is available when the client class has an interface specification, otherwise it is not available.

2.2.4 object adapter mode

Implementation method: the object adapter mode can introduce the implemented components in the existing component library into the adapter class, which also implements the business interface of the current system.

The code is as follows:

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

//Create adapter object (SD compatible 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);
    }
}

//Test class
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 mode is the interface Adapter mode. 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.

2.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 that of the new system.
  • Components provided by a third party are used, but the component interface definition is different from that required by itself.

2.2.6 JDK source code analysis

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

InputStreamReader inherits from Java The Reader in the IO package implements the abstract and unimplemented methods in it. For example:

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);
}
  • InputStreamReader encapsulates the StreamDecoder that also implements the Reader.
  • StreamDecoder is not the content of Java SE API, but its own implementation given by sun JDK. But we know that they encapsulate the byte stream class in the construction method, and decode and convert between byte stream and character stream through this class.

Conclusion:

​ From the surface level, InputStreamReader does the conversion from InputStream byte stream class to Reader character stream. As can be seen from the implementation class relationship structure in Sun JDK above, the design and implementation of StreamDecoder actually adopts the adapter mode.

3.3 decorator mode

5.3.1 general

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

Fast food restaurants have fried noodles and fried rice. They can add eggs, ham and bacon as side dishes. Of course, adding side dishes requires extra money. The price of each side dish is usually different, so it will be more troublesome to calculate the total price.

Problems with 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 subclasses.

  • Too many subclasses generated

definition:

​ It refers to the mode of dynamically adding some responsibilities (i.e. adding additional functions) to the object without changing the existing object structure.

5.3.2 structure

Roles in Decorator mode:

  • Abstract Component role: define an abstract interface to standardize objects ready to receive additional responsibilities.
  • Concrete component role: implement abstract components and add some responsibilities to them by decorating roles.
  • Abstract Decorator role: inherits or implements abstract components and contains instances of specific components. The functions of specific components can be extended through their subclasses.
  • Concrete decorator role: implement relevant methods of abstract decoration and add additional responsibilities to specific component objects.

5.3.3 cases

We use the decorator model to improve the cases of fast food restaurants and experience the essence of the decorator model.

The code is as follows:

//Fast food interface
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();  //Get price
}

//Fried rice
public class FriedRice extends FastFood {

    public FriedRice() {
        super(10, "Fried rice");
    }

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

//Stir-Fried Noodles with Vegetables
public class FriedNoodles extends FastFood {

    public FriedNoodles() {
        super(12, "Stir-Fried Noodles with Vegetables");
    }

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

//Ingredients
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;
    }
}

//Egg ingredients
public class Egg extends Garnish {

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

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

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

//Bacon ingredients
public class Bacon extends Garnish {

    public Bacon(FastFood fastFood) {

        super(fastFood,2,"Bacon");
    }

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

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

//Test class
public class Client {
    public static void main(String[] args) {
        //Order a fried rice
        FastFood food = new FriedRice();
        //Price spent
        System.out.println(food.getDesc() + " " + food.cost() + "element");

        System.out.println("========");
        //Order a fried rice with eggs
        FastFood food1 = new FriedRice();

        food1 = new Egg(food1);
        //Price spent
        System.out.println(food1.getDesc() + " " + food1.cost() + "element");

        System.out.println("========");
        //Order a fried noodles with bacon
        FastFood food2 = new FriedNoodles();
        food2 = new Bacon(food2);
        //Price spent
        System.out.println(food2.getDesc() + " " + food2.cost() + "element");
    }
}

Benefits:

  • Decorator mode can bring more flexible extension functions than inheritance, and it is more convenient to use. You can obtain diversified results with different behavior states by combining different decorator objects. Decorator mode has better expansibility than inheritance, and perfectly follows the opening and closing principle. Inheritance is a static additional responsibility, while decorator is a dynamic additional responsibility.
  • Decorative classes and decorated classes can develop independently and will not be coupled with each other. Decorative pattern is an alternative pattern of inheritance. Decorative pattern can dynamically expand the functions of an implementation class.

5.3.4 usage 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 cannot be used:

    • The first is that there are a large number of independent extensions in the system. In order to support each combination, a large number of subclasses will be generated, resulting in an explosive increase in the number of subclasses;
    • The second type is because the class definition cannot inherit (such as final class)
  • Add responsibilities to a single object in a dynamic and transparent manner without affecting other objects.

  • When the functional requirements of an object can be added or revoked dynamically.

5.3.6 difference between agent and decorator

Differences between static proxy and decorator modes:

  • Similarities:
    • All of them should 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:
    • Different purposes
      The decorator is to enhance the target object
      Static proxy is to protect and hide the target object
    • Getting the target object is built differently
      The decorator is passed in from the outside, which can be passed through the construction method
      Static proxy is created inside the proxy class to hide the target object

4.4 bridging mode

4.4.1 general

Now there is a requirement to create different graphics, and each graphics may have different colors. We can use inheritance to design class relationships:

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

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

definition:

​ Separate abstraction from implementation so that they can change independently. It is implemented by replacing inheritance relationship with composition relationship, which reduces the coupling between abstraction and implementation.

4.4.2 structure

The Bridge mode contains the following main roles:

  • Abstraction role: defines an abstract class and contains a reference to the implementation object.
  • Refined abstraction role: it is a subclass of the abstract role, implements the business methods in the parent class, and calls the business methods in the abstract role through the composition relationship.
  • Implementer role: defines the interface of the implementer role, which can be called by the extended abstract role.
  • Concrete implementer role: give the concrete implementation of the implementation role interface.

4.4.3 cases

[example] video player

A cross platform video player needs to be developed, which can play video files in various formats on different operating system platforms (such as Windows, Mac, Linux, etc.), and the common video formats include RMVB, AVI, WMV, etc. The player contains two dimensions and is suitable for using bridging mode.

The code is as follows:

//video file 
public interface VideoFile {
    void decode(String fileName);
}

//avi file
public class AVIFile implements VideoFile {
    public void decode(String fileName) {
        System.out.println("avi Video files:"+ fileName);
    }
}

//rmvb file
public class REVBBFile implements VideoFile {

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

//Operating system version
public abstract class OperatingSystemVersion {

    protected VideoFile videoFile;

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

    public abstract void play(String fileName);
}

//Windows version
public class Windows extends OperatingSystem {

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

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

//mac version
public class Mac extends OperatingSystemVersion {

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

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

//Test class
public class Client {
    public static void main(String[] args) {
        OperatingSystem os = new Windows(new AVIFile());
        os.play("Warwolf 3");
    }
}

Benefits:

  • The bridging mode improves the scalability of the system. If one of the two change dimensions is extended arbitrarily, there is no need to modify 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.

  • Make details transparent to customers

4.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 the number of system classes increases sharply 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 relationship between the two levels, and make them establish an association relationship in the abstract level through bridging mode.

5.5 appearance mode

5.5.1 general

Some people may have fired stocks, but in fact most people do not quite understand it. This kind of stock is easy to lose if they do not know enough about securities knowledge. When they start stocks, they will surely think, if there is a knowledgeable helper, the fund is a good helper. Fu Baoli has many funds, which centralize the funds scattered by investors. It is managed by professional managers and invested in stocks, bonds, foreign exchange and other fields. The income from fund investment belongs to the holder, and the management organization charges a certain proportion of custody management fees.

definition:

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

5.5.2 structure

The Facade mode contains the following main roles:

  • Facade role: provide a common interface for multiple subsystems.
  • Sub System role: realize some functions of the system, and customers can access it through appearance role.

5.5.3 cases

[example] intelligent appliance control

Xiao Ming's grandfather is 60 years old. He lives at home alone: he needs to turn on the light, turn on the TV and turn on the air conditioner every time; Turn off the lights, turn off the TV and turn off the air conditioner when sleeping; The operation is quite troublesome. So Xiao Ming bought a smart speaker for his grandfather, which can directly control the opening and closing of these smart appliances through voice. Class diagram is as follows:

The code is as follows:

//Lamps
public class Light {
    public void on() {
        System.out.println("Turn on the light....");
    }

    public void off() {
        System.out.println("Turn off the lights....");
    }
}

//Television
public class TV {
    public void on() {
        System.out.println("Turned on the TV....");
    }

    public void off() {
        System.out.println("Turned off the TV....");
    }
}

//control class
public class AirCondition {
    public void on() {
        System.out.println("Turn on the air conditioner....");
    }

    public void off() {
        System.out.println("Turn off the air conditioner....");
    }
}

//Intelligent speaker
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("open")) {
            on();
        } else if(message.contains("close")) {
            off();
        } else {
            System.out.println("I still don't understand what you said!!!");
        }
    }

    //Turn on the appliance with one button after getting up
    private void on() {
        System.out.println("Wake up");
        light.on();
        tv.on();
        airCondition.on();
    }

    //Switch off the electric appliance with one key when sleeping
    private void off() {
        System.out.println("I went to sleep");
        light.off();
        tv.off();
        airCondition.off();
    }
}

//Test class
public class Client {
    public static void main(String[] args) {
        //Create appearance objects
        SmartAppliancesFacade facade = new SmartAppliancesFacade();
        //The client interacts directly with the appearance object
        facade.say("Turn on the appliance");
        facade.say("Turn off appliances");
    }
}

Benefits:

  • It reduces the coupling between the subsystem and the client, so that the change of the subsystem will not affect the client class calling it.
  • It shields the subsystem components from customers, reduces the number of objects handled by customers, and makes the subsystem easier to use.

Disadvantages:

  • It does not comply with the opening and closing principle, and the modification is very troublesome

5.4.4 usage scenarios

  • When building a hierarchical system, defining the entry point of each layer in the subsystem using appearance patterns can simplify the dependencies between subsystems.

  • When there are many subsystems in a complex system, appearance mode can design a simple interface for the system for external access.

  • When there is a great relationship between the client and multiple subsystems, the appearance pattern can be introduced to separate them, so as to improve the independence and portability of the subsystem.

    5.6 combination mode

    5.6.1 general

    definition:

    ​ Also known as partial whole pattern, it is used to treat a group of similar objects as a single object. The combination mode combines objects according to the tree structure, which is used to represent the partial and overall levels. This type of design pattern belongs to structural pattern, which creates a tree structure of object groups.

    5.6.2 structure

    The combination mode mainly includes three roles:

    • Abstract root node (Component): defines the common methods and attributes of objects at all levels of the system, and some default behaviors and attributes can be defined in advance.
    • Composite: define the behavior of branch nodes, store child nodes, and combine branch nodes and leaf nodes to form a tree structure.
    • Leaf node: leaf node object, under which there is no branch, is the smallest unit of system level traversal.

    5.6.3 case realization

    [example] software menu

    As shown in the figure below, we can often see similar menus when accessing some other management systems. A menu can contain menu items (menu items refer to menu items that no longer contain other contents) or menus with other menu items. Therefore, it is appropriate to use the combination mode to describe the menu. Our requirement is to print out all the menus and menu item names for a menu.

    Code implementation:

    Both menus and menu items should inherit from the unified interface, which is called menu component.

    //Menu components, whether menus or menu items, should inherit this class
    public abstract class MenuComponent {
    
        protected String name;
        protected int level;
    
        //add menu
        public void add(MenuComponent menuComponent){
            throw new UnsupportedOperationException();
        }
    
        //Remove menu
        public void remove(MenuComponent menuComponent){
            throw new UnsupportedOperationException();
        }
    
        //Gets the specified submenu
        public MenuComponent getChild(int i){
            throw new UnsupportedOperationException();
        }
    
        //Get menu name
        public String getName(){
            return name;
        }
    
        public void print(){
            throw new UnsupportedOperationException();
        }
    }
    

    The MenuComponent here is defined as an abstract class. Because there are some common properties and behaviors to be implemented in this class, the Menu and MenuItem classes can only cover the methods they are interested in, rather than the methods they are not interested in. For example, the Menu class can contain submenus, so they need to cover the add(), remove(), getChild() methods, But MenuItem should not have these methods. The default implementation given here is to throw exceptions. You can also rewrite the default implementation according to your own needs.

    public class Menu extends MenuComponent {
    
        private List<MenuComponent> menuComponentList;
    
        public Menu(String name,int level){
            this.level = level;
            this.name = name;
            menuComponentList = new ArrayList<MenuComponent>();
        }
    
        @Override
        public void add(MenuComponent menuComponent) {
            menuComponentList.add(menuComponent);
        }
    
        @Override
        public void remove(MenuComponent menuComponent) {
            menuComponentList.remove(menuComponent);
        }
    
        @Override
        public MenuComponent getChild(int i) {
            return menuComponentList.get(i);
        }
    
        @Override
        public void print() {
    
            for (int i = 1; i < level; i++) {
                System.out.print("--");
            }
            System.out.println(name);
            for (MenuComponent menuComponent : menuComponentList) {
                menuComponent.print();
            }
        }
    }
    

    The Menu class has implemented all methods except the getName method, because the Menu class has the functions of adding menus, removing menus and obtaining submenus.

    public class MenuItem extends MenuComponent {
    
        public MenuItem(String name,int level) {
            this.name = name;
            this.level = level;
        }
    
        @Override
        public void print() {
            for (int i = 1; i < level; i++) {
                System.out.print("--");
            }
            System.out.println(name);
        }
    }
    

    MenuItem is a menu item and cannot have sub menus. Therefore, the functions of adding menus, removing menus and obtaining sub menus cannot be realized.

    5.6.4 classification of combination mode

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

    • Transparent combination mode

      In the transparent composition mode, all methods for managing member objects are declared in the abstract root node role. For example, in the example, MenuComponent declares add, remove and getChild methods. The advantage of this is to ensure that all component classes have the same interface. Transparent composite mode is also the standard form of composite mode.

      The disadvantage of transparent composition mode is that it is not safe enough, because leaf objects and container objects are different in essence. Leaf objects cannot have objects at the next level, that is, they cannot contain member objects. Therefore, it is meaningless to provide them with methods such as add(), remove(), which will not make mistakes in the compilation stage, However, in the run-time phase, if these methods are called, errors may occur (if no corresponding error handling code is provided)

    • Safe combination mode

      In the safe composition 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 of the security composition mode is that it is not transparent enough, because the leaf component and the container component have different methods, and the methods used to manage member objects in the container component are not defined in the abstract component class. Therefore, the client cannot program completely for the abstract, and must treat the leaf component and the container component differently.

    5.6.5 advantages

    • Composite mode can clearly define hierarchical complex objects and represent all or part of the hierarchy of objects. It allows the client to ignore the hierarchy differences and facilitate the control of the whole hierarchy.
    • The client can consistently use a composite structure or a single object in it, regardless of whether it is dealing with a single object or the whole composite structure, which simplifies the client code.
    • It is 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".
    • The combination pattern provides a flexible solution for the object-oriented implementation of tree structure. Through the recursive combination of leaf nodes and branch nodes, complex tree structure can be formed, but the control of tree structure is very simple.

    5.6.6 usage scenarios

    The combination mode is born in response to the tree structure, so the use 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.

    5.7 yuan sharing mode

    5.7.1 general

    definition:

    ​ Sharing technology is used to effectively support the reuse of a large number of fine-grained objects. By sharing existing objects, it can greatly reduce the number of objects to be created and avoid the overhead of a large number of similar objects, so as to improve the utilization of system resources.

    5.7.2 structure

    There are two states in Flyweight mode:

    1. Internal state, that is, shareable parts that will not change with the change of environment.
    2. External state refers to the unshareable part that changes with the environment. The key to the implementation of shared meta mode is to distinguish these two states in the application and externalize the external state.

    The main roles of Xiangyuan mode are as follows:

    • Abstract shared meta role (Flyweight): it is usually an interface or abstract class. The abstract shared meta class declares the public methods of the specific shared meta class. These methods can provide the internal data (internal state) of the shared meta object to the outside world, and can also set the external data (external state) through these methods.
    • Concrete Flyweight role: it implements the abstract flyweight class, which is called the flyweight object; Storage space is provided for internal states in specific meta classes. Generally, we can design specific meta classes in combination with the singleton pattern to provide unique meta objects for each specific meta class.
    • Unsharable flyweight role: not all subclasses of the abstract shared primitive class need to be shared. Subclasses that cannot be shared can be designed as unshared concrete shared primitive classes; When you need an object that does not share a specific meta class, you can create it directly by instantiation.
    • Flyweight Factory role: responsible for creating and managing Flyweight Factory roles. When a customer object requests a meta object, the meta factory checks whether there are qualified meta objects in the system, and if so, provides them to the customer; If it does not exist, a new meta object is created.

    5.7.3 case realization

    Tetris

    The following picture is a well-known Tetris box. If each different box is an instance object in the Tetris game, these objects will occupy a lot of memory space. The following is implemented using the sharing mode.

    The code is as follows:

    Tetris has different shapes. We can extract AbstractBox from these shapes to define common attributes and behaviors.

    public abstract class AbstractBox {
        public abstract String getShape();
    
        public void display(String color) {
            System.out.println("Square shape:" + this.getShape() + " Color:" + color);
        }
    }
    

    The next step is to define different shapes, such as IBox class, LBox class, OBox class, etc.

    public class IBox extends AbstractBox {
    
        @Override
        public String getShape() {
            return "I";
        }
    }
    
    public class LBox extends AbstractBox {
    
        @Override
        public String getShape() {
            return "L";
        }
    }
    
    public class OBox extends AbstractBox {
    
        @Override
        public String getShape() {
            return "O";
        }
    }
    

    A factory class (BoxFactory) is provided to manage meta objects (i.e. AbstractBox subclass objects). There is only one factory class object, so the singleton mode can be used. And provide a method to get the shape for the factory class.

    public class BoxFactory {
    
        private static HashMap<String, AbstractBox> map;
    
        private BoxFactory() {
            map = new HashMap<String, AbstractBox>();
            AbstractBox iBox = new IBox();
            AbstractBox lBox = new LBox();
            AbstractBox oBox = new OBox();
            map.put("I", iBox);
            map.put("L", lBox);
            map.put("O", oBox);
        }
    
        public static final BoxFactory getInstance() {
            return SingletonHolder.INSTANCE;
        }
    
        private static class SingletonHolder {
            private static final BoxFactory INSTANCE = new BoxFactory();
        }
    
        public AbstractBox getBox(String key) {
            return map.get(key);
        }
    }
    

    5.7.5 advantages, disadvantages and usage scenarios

    1. Advantages

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

    2. Disadvantages:

    In order to make objects can be shared, it is necessary to externalize some states of shared meta objects, separate internal states from external states, and make program logic complex

    3. Usage scenario:

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

    5.7.6 JDK source code analysis

    The Integer class uses meta mode. Let's look at the following example first:

    public class Demo {
        public static void main(String[] args) {
            Integer i1 = 127;
            Integer i2 = 127;
    
            System.out.println("i1 and i2 Is the object the same object?" + (i1 == i2));
    
            Integer i3 = 128;
            Integer i4 = 128;
    
            System.out.println("i3 and i4 Is the object the same object?" + (i3 == i4));
        }
    }
    

    Run the above code and the results are as follows:

    Why does the first output statement output true and the second output statement output false? Decompile through decompile software, and the code is as follows:

    public class Demo {
        public static void main(String[] args) {
            Integer i1 = Integer.valueOf((int)127);
            Integer i2 Integer.valueOf((int)127);
            System.out.println((String)new StringBuilder().append((String)"i1\u548ci2\u5bf9\u8c61\u662f\u5426\u662f\u540c\u4e00\u4e2a\u5bf9\u8c61\uff1f").append((boolean)(i1 == i2)).toString());
            Integer i3 = Integer.valueOf((int)128);
            Integer i4 = Integer.valueOf((int)128);
            System.out.println((String)new StringBuilder().append((String)"i3\u548ci4\u5bf9\u8c61\u662f\u5426\u662f\u540c\u4e00\u4e2a\u5bf9\u8c61\uff1f").append((boolean)(i3 == i4)).toString());
        }
    }
    

    As can be seen from the above code, the underlying operation of directly assigning basic data type data to Integer type variables uses valueOf(), so you only need to look at this method

    public final class Integer extends Number implements Comparable<Integer> {
        
    	public static Integer valueOf(int i) {
            if (i >= IntegerCache.low && i <= IntegerCache.high)
                return IntegerCache.cache[i + (-IntegerCache.low)];
            return new Integer(i);
        }
        
        private static class IntegerCache {
            static final int low = -128;
            static final int high;
            static final Integer cache[];
    
            static {
                int h = 127;
                String integerCacheHighPropValue =
                    sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
                if (integerCacheHighPropValue != null) {
                    try {
                        int i = parseInt(integerCacheHighPropValue);
                        i = Math.max(i, 127);
                        // Maximum array size is Integer.MAX_VALUE
                        h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                    } catch( NumberFormatException nfe) {
                    }
                }
                high = h;
                cache = new Integer[(high - low) + 1];
                int j = low;
                for(int k = 0; k < cache.length; k++)
                    cache[k] = new Integer(j++);
                // range [-128, 127] must be interned (JLS7 5.1.7)
                assert IntegerCache.high >= 127;
            }
    
            private IntegerCache() {}
        }
    }
    

    You can see that Integer creates and caches Integer objects between - 128 ~ 127 by default. When calling valueOf, if the parameter is between - 128 ~ 127, the subscript is calculated and returned from the cache. Otherwise, a new Integer object is created.

Added by Wildthrust on Fri, 21 Jan 2022 23:01:42 +0200