Detailed explanation of design mode structural mode - Xiangyuan mode

Posted by revraz on Sat, 22 Jan 2022 10:37:48 +0100

Complete notes Directory: Contents of notes on detailed explanation of design pattern , welcome advice!

Structural mode describes how to form a larger structure of classes or objects according to a certain layout. There are two types:

  • Class structured pattern: uses inheritance mechanism to organize interfaces and classes.
  • Object structured pattern: use combination 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

5.7 yuan sharing mode

5.7.1 general

Meta sharing mode: use sharing technology to effectively support the reuse of a large number of fine-grained objects. By sharing existing objects, we 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.

Class diagram is as follows:

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

/**
 * Abstract meta role
 */
public abstract class AbstractBox {
    // Method of obtaining graphics
    public abstract String getShape();
    // Display graphics and colors
    public void display(String color) {
        System.out.println("Square shape:" + getShape() + ", Color:" + color);
    }
}

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

/**
 * Specific meta role
 */
public class IBox extends AbstractBox {
    public String getShape() {
        return "I";
    }
}
public class LBox extends AbstractBox {
    public String getShape() {
        return "L";
    }
}
public class OBox extends AbstractBox {
    public String getShape() {
        return "O";
    }
}

A factory class BoxFactory is provided to manage meta objects (i.e. AbstractBox subclass objects). Only one factory class object is required, so the singleton mode can be used, and a method to obtain shapes is provided for the factory class.

public class BoxFactory {
    private static BoxFactory factory = new BoxFactory();
    private HashMap<String, AbstractBox> map;
    // Initialize in the constructor
    private BoxFactory() {
        map = new HashMap<>();
        map.put("I", new IBox());
        map.put("L", new LBox());
        map.put("O", new OBox());
    }
    // Provide a method to get the factory class object
    public static BoxFactory getInstance() {
        return factory;
    }
    // Get drawing objects by name
    public AbstractBox getShape(String name) {
        return map.get(name);
    }
}

Test class:

public class Client {
    public static void main(String[] args) {
        // Get I drawing objects
        AbstractBox box1 = BoxFactory.getInstance().getShape("I");
        box1.display("grey");
        // Get L drawing objects
        AbstractBox box2 = BoxFactory.getInstance().getShape("L");
        box2.display("green");
        // Get O drawing objects
        AbstractBox box3 = BoxFactory.getInstance().getShape("O");
        box3.display("grey");
        // Get O drawing objects
        AbstractBox box4 = BoxFactory.getInstance().getShape("O");
        box4.display("gules");

        System.out.println("Obtained twice O Whether the drawing object is the same object:" + (box3 == box4));
    }
}

5.7.4 advantages, disadvantages and usage scenarios

advantage:

  • 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

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

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.5 JDK source code analysis

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

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));
    }
}

The operation 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.

Topics: Design Pattern