Structural Pattern 2 Other Patterns

Article directory

1. Bridge Mode

​ A class has two (or more) dimensions that vary independently. We allow these two (or more) dimensions to be independently extended by combining them.

​ The design principle of “composition is better than inheritance” replaces the inheritance relationship with the composition relationship to avoid the exponential explosion of the inheritance hierarchy.

When a class has two or more changing dimensions, using the bridge pattern can [decouple] these changing dimensions and stabilize the high-level code architecture.

Difference between bridge mode and strategy mode

The bridge mode emphasizes the separation of abstraction and implementation, and the strategy mode emphasizes the encapsulation and mutual replacement of algorithms. The former is a structural mode, and the latter is a behavioral mode.

1.1 Role Responsibilities

Looking at the UML class diagram above, we can find that the bridge pattern is divided into the following four roles:

Role duty
Abstraction Define an abstract interface with an object reference of Implementor type
RefinedAbstraction Extend the interface definition in Abstraction
Implementor The implementation part can be an interface or an abstract class, and its methods do not have to be consistent with those in the abstract part. Generally, the implementation part provides basic operations, while the abstract part defines business methods based on the basic operations of the implementation part.
Implemented roles Implement the Implementor interface and give the specific implementation

Let mobile phones be classified both by brand and software type.

1.2 Usage scenarios

​ Different brands have different compatibility support for different software. At first, the game function was added for the A brand.

public class ABrandGame {

    public void run(){System.out 
        .println ( "A brand game is running" );
    }
}

The new requirement is to add game functions for the B brand. Based on the object-oriented understanding, an interface needs to be abstracted. The AB brand has this interface. The UML class diagram is as follows:

public interface BrandPhone {
    /**
     * run
     */
    void run();
}

Adding a call function, which is required for both brands A and B, means that the interface is a brand, and there are abstract subclasses A and B under it, and there are corresponding games and communication functions under it. The UML class diagram is as follows:

In this way, adding more functions and more brands will increase the expansion of the class exponentially. Because the strong coupling relationship of inheritance is used here, the disadvantages are obvious when the business is too complicated. One dimension of change is brand, and the other is software function. Using the bridge mode can solve the problem of deep inheritance and strong coupling.

Use of bridge mode

2 Decorator pattern

The decorator pattern is a pattern for decorating something. From the code level, it is an extension or modification of the class. From the traditional method, we can use inheritance to extend a certain class, but it often leads to a lot of subclasses. If we want to To avoid this situation, we can use the decorator pattern in the [design pattern.]

2.1 Example

Let’s take the milk tea that girls like to drink as an example, and define a milk tea interface MilkyTea

public interface MilkyTea {
    /**
     * Make milk tea
     */
    public void make();
}

Realize coconut milk tea, pearl milk tea, taro ball milk tea

public  class  CoconutMilkyTea  implements  MilkyTea {
     //Make coconut milk tea 
    @Override 
    public  void  make () {
       System.out.println( "Make ordinary milk tea" );
       System.out.println( "Add grated coconut" );
    }
}

public  class  PearlMilkyTea  implements  MilkyTea  {
     //Make pearl milk tea 
    @Override 
    public  void  make ()  {
        System.out.println( "Make ordinary milk tea" );
        System.out.println( "Add Pearl" );
    }
}

public  class  TaroBallsMilkyTea  implements  MilkyTea  {
     //Make taro ball milk tea 
    @Override 
    public  void  make ()  {
        System.out.println( "Make ordinary milk tea" );
        System.out.println( "Add Taro Balls" );
    }
}

Demand changes, in addition to ordinary types of milk tea, adding half-sugar options for health, and adding iced milk tea to cope with high temperature , according to the above logic, the above three milk teas need to derive corresponding half-sugar products and cold drink products, so as to meet the above demand products The class will become nine classes of 3*3

If you add more milk tea or product features, oh my god, the number of categories will increase exponentially. This is where the decorator pattern comes into play.

The decorator pattern mainly solves the problem that the inheritance relationship is too complicated, and replaces inheritance through composition. Its function is as follows:

  • Add enhancements to the original class

  • Multiple decorators can be nested on primitive classes

The business is reconstructed here, and the above-mentioned materials are extracted to process ‘decorative’ ingredients such as pearls, taro balls, and coconut paste, and provide the realization of the initial milk tea interface. If you need to add materials, you can enrich the functions through the assembly mode.

implement interface

public class NormalMilkyTea implements MilkyTea {
    @Override
    public void make() {
        System.out.println( "Make ordinary milk tea" );
    }
}

Assembly class

//Pearl decoration class 
public  class  PearlDecorator  implements  MilkyTea  {
     //Holds a primitive class that needs to be enhanced 
    private MilkyTea milkyTea;

    public PearlDecorator(MilkyTea milkyTea){
        this.milkyTea = milkyTea;
    }
    @Override 
    public  void  make ()  {
         //Original class function
        milkyTea.make();
        //Decoration Enhancement 
        System.out.println( "Add Pearl" );
    }
}

//Other decorative classes are shown above

unit test

@Test 
public  void  test () {
     //Make pearl taro ball milk tea 
    //Normal milk tea 
    MilkyTea milkyTea = new NormalMilkyTea();
     // 
    HalfDecorator sugarHalfMilyTea = new SugarHalfDecorator(milkyTea);
     // 
    PearlDecorator pearMilyTea = new PearlDecorator (sugarHalfMilyTea);
     // Taro Ball Decoration 
    TaroBallsDecorator taroBallsMilyTea = new TaroBallsDecorator(pearMilyTea);
     // Coconut Decoration 
    CoconutDecorator coconutMilyTea = new CoconutDecorator(pearMilyTea);
     // Cold Drink Decoration 
    ColdDecorator coldMilyTea =new ColdDecorator(coconutMilyTea);
    coldMilyTea.make();
}

After using the assembly mode, you can flexibly configure and make various milk tea varieties, and you only need to implement a decoration class for new milk tea materials.

2.2 Role Responsibilities

The decorator pattern mainly includes the following roles.

  1. Abstract Component role: Define an abstract interface to standardize objects that are ready to receive additional responsibilities.
  2. ConcreteComponent roles: Implement abstract components and add some responsibilities to them by decorating roles.
  3. Abstract Decorator role: inherits abstract components and contains instances of concrete components, and can extend the functions of concrete components through its subclasses (in the above example, there is no abstract decoration role because there is only one interface).
  4. ConcreteDecorator role: Implements methods related to abstract decoration and adds additional responsibilities to concrete component objects.

2.3 Use in the framework

​ java’s IO stream uses the decoration pattern

Similarities and differences between decorator and proxy patterns

  • Same: both are functional enhancements.
  • Different: The decorator is an enhancement to the related functions of the original class and the proxy pattern is an enhancement to the non-related functions.

3. Adapter Mode

Generally speaking, the adapter mode can be regarded as a “compensation mode” to remedy the design flaws. Applying this model is a “helpless move”.

3.1 Role Responsibilities

Related role

The adapter pattern (Adapter) includes the following main roles.

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

Application scenarios :

  • Secondary encapsulation of external system interfaces , abstracting interface methods that meet our business needs
  • The integration and adaptation of multiple implementation frameworks of the same function point , such as the adaptation example of multiple log systems sl4j.
  • Compatible with old version interface
  • Adapt to data in different formats

Two implementations of adapters

  • Class Adapter: The main method of use is inheritance, Adapter (adapter) inherits Adaptee (adapter class)

  • For adapters: object adapters are implemented using composition relationships, and Adapter (adapter) holds the Adaptee (adapter class) member variable.

3.2 Examples

​ Electric shavers, mobile phones, computers, tablets and other household appliances require different voltages for charging, but the State Grid uniformly provides 220V voltage, so you need to use adapters for processing.

target interface

// Charger interface 
public  interface  Charger {
     /**
     * Charging function
     */
    void charge();
}

adaptor

//pc adapter 
public  class  PcCharge {
     public  void  chargePc (){System.out 
        .println ( "180V voltage to charge the computer" );
    }
}
//Mobile phone adapter 
public  class  PhoneCharge {
     public  void  chargePhone (){
        System.out.println ( " 5V voltage to charge the phone" );
    }
}

adapter

//pc power adapter (object adaptation) 
public  class  PcChargeAdaptor  implements  Charger  {
     //Object adaptation, holding the adapter in the form of a combination, the Charger interface needs to be implemented 
    private PcCharge pcCharge;
     @Override 
    public  void  charge ()  {
        System.out.println( "220V voltage is converted to 180V" );
        pcCharge.chargePc();
    }
}

//The inheritance form of the pc power adapter (class adaptation) has the adapter method, and the function of the same name does not need to be implemented 
public  class  PhoneChargeAdaptor  extends  PhoneCharge  implements  Charger  {
     @Override 
    public  void  charge ()  {
        System.out.println( "220V voltage converted to 5V" );
        chargePhone();
    }
}

UML class diagram

3.3 Applications in the framework

  • slf4j provides adaptation for logback, log4j, apache log, etc.

  • In order to be compatible with the Enumeration in the 1.0 period, the adapter mode is used in the Collections class, which is compatible with the old and new versions. The object adaptation mode is used here, and iterator is used instead of Enumeration

4 Facade Mode

4.1 Role Responsibilities

​ Facade mode, also known as appearance mode, the full English name is Facade Design Pattern. Facade pattern provides a set of unified interfaces for subsystems, and defines a set of high-level interfaces to make subsystems easier to use. To put it bluntly, the facade is to simplify the complexity of interface calls in the case of fine-grained interfaces .

effect

  • Solve usability problems, hide underlying details, reduce complexity, and provide easy-to-use high-level interface assembly.
Role duty
Facade Also known as the facade role, the unified interface to the outside of the system
Subsystem role (SubSystem) There can be one or more SubSystems at the same time

4.2 Examples

This example takes the service provided by a restaurant as an example. Customers do not need to take the initiative to explain to the chef in the kitchen when ordering food. They only need to explain their demands to the waiter (front role) of the restaurant and wait. They do not need to pay attention to the chef and ingredients and wait for their own. Meals can be served in front of you

Chef interface and implementation

public interface Chef {
    void cook();
}
// Cold Chef 
public  class  ColdDishChef  implements  Chef  {
     @Override 
    public  void  cook ()  {
        System.out.print( "Make cucumber salad" );
    }
}
// Pastry Chef 
public  class  PastryChef  implements  Chef  {
     @Override 
    public  void  cook ()  {
        System.out.print( "Make waffle" );
    }
}
// StirFryChef 
public  class  StirFryChef  implements  Chef  {
     @Override 
    public  void  cook ()  {
        System.out.print( "Make roasted whole lamb" );
    }
}

waiter facade

public class Waiter {
    private ColdDishChef coldDishChef = new ColdDishChef();
    private StirFryChef stirFryChef = new StirFryChef();
    private PastryChef pastryChef = new PastryChef();

    //Package A Roasted Whole Lamb with Salad Cucumbers 
    public  void  foodMenuA (){
        System.out .print ( "Package A:" );
        coldDishChef.cook();
        stirFryChef.cook();
        System.out.println();
    }

    //Package B waffle roast whole lamb 
    public  void  foodMenuB (){
        System.out.print ( " Package B:" );
        pastryChef.cook();
        stirFryChef.cook();
        System.out.println();
    }

    //Package B Waffle Roasted Whole Lamb Salad with Cucumber 
    public  void  foodMenuC (){
        System.out.print ( " Package C:" );
        coldDishChef.cook();
        pastryChef.cook();
        stirFryChef.cook();
        System.out.println();
    }
}

4.3 Applications in the framework

5 Combination Mode

5.1 Role Responsibilities

​ Organize a set of objects (Compose) into a tree structure to represent a “part-whole
” hierarchy. Composition allows clients to unify the processing logic of individual objects and composite objects.
Such as files (single objects) and directories (composite objects) in the file system, sub-departments (composite objects) and employees (single objects) in an organizational structure.

Role duty
Abstract component (Component) role Declare a common interface for composite objects and leaf objects, so that clients can access and manage the entire object tree through this interface, and provide default implementations for these defined interfaces.
Composite role Subcomponents (composite objects, leaf objects) are usually stored, the behavior of those components that contain subcomponents is defined, and subcomponent-related operations defined in abstract components are implemented, such as adding (addChild) and removing (removeChild) of subcomponents ) Wait.
Leaf role Defines and implements the behavior of a leaf object, and it no longer contains other child node objects
Client role Through the Component interface, the combined object and the leaf object are operated uniformly to create the entire object tree structure.

5.2 Examples

Implement the personnel structure diagram of the entire company (department, employee affiliation), and provide an interface to calculate the salary cost of the department.

Department and Employee Abstraction Components

public abstract class Component {
    protected long id;
    protected double salary;
    public Component(long id){
        this.id = id;
    }
    public Component(long id,Double salary){
        this.id = id;
        this.salary = salary;
    }
    /**
     * Calculate salary
     */
    abstract  Double calcSalary();
}

department

public class Depart extends Component {
    private List<Component> staffList = new ArrayList<>();

    public Depart(long id){
        super(id);
    }
    public Depart(long id,List<Component> staffList){
        super(id);
        this.staffList.addAll(staffList);
    }

    public void addStaff(Component staff){
        this.staffList.add(staff);
    }

    public  void  addStaffList (List<Component> staffList) {
         this .staffList.addAll(staffList);
    }
    @Override
    public Double calcSalary() {
        Double totalSalary = 0D;
        this.salary = totalSalary;
        if(CollectionUtils.isEmpty(staffList)){
            return totalSalary;
        }
        for(Component staff:staffList){
            totalSalary +=staff.calcSalary();

        }
        this.salary = totalSalary;
        return totalSalary;
    }
}

Staff

public class Staff extends Component {
    public Staff(long id,Double salary) {
        super(id,salary);
    }
    @Override
    public Double calcSalary() {
        return this.salary;
    }
}

5.3 Summary

The design idea of ​​​​the combination pattern is not so much a [design pattern] as it is an abstraction of a data structure and algorithm for business scenarios. Among them, the data can be represented as a data structure such as a tree, and business requirements can be realized through a recursive traversal algorithm on the tree.

For the same object, an instance can be kept in memory for more use. For similar objects, the same parts (fields) in these objects can also be extracted and designed as a flyweight, so that these large numbers of similar objects can refer to these flyweights.

6 [Flyweight Mode]

6.1 Role Responsibilities

​ Flyweight mode For a large number of repeated objects that may appear, extracting its immutable attributes to form an immutable object is the flyweight object. The intention of the Flyweight pattern is to reuse objects and save memory, provided that the Flyweight object is an immutable object.

6.2 Examples

​ Let’s take a large-scale chess as an example. The game has thousands of rooms, each room has a chess game, and each chess game has several pieces. If these objects are created, it will lead to huge similar objects in the program, but the analysis finds that each The color (red square, black square) and text (rook, gun, phase) of each chess piece are the same, then we can extract it to create a flyweight object.

flyweight object

public  class  ChessPieceUnit { 
    //Unique identifier 
    private Integer id;
     //Text car, horse and gun 
    private String text;
     //Red square, black square 
    private String color;

    public ChessPieceUnit(Integer id, String text, String color) {
        this.id = id;
        this.text = text;
        this.color = color;
    }

Flyweight Object Factory

public class ChessPieceUnitFactory {
     private static Map<Integer, ChessPieceUnit> chessPieces = new HashMap<>();
     static{
         chessPieces.put( 1 , new ChessPieceUnit( 1 , "car" , "red" ));
         chessPieces.put( 1 , new ChessPieceUnit( 1 , "horse" , "black" ));
          //...omit other pieces
     }

     public static ChessPieceUnit  getChessUnit(Integer chessPieceId){
         ChessPieceUnit chessPieceUnit = chessPieces.get(chessPieceId);
         return chessPieceUnit;
     }
}

non-flyweight object

public  class  ChessPiece { 
    //Flyweight object 
    private ChessPieceUnit chessPieceUnit;
     //The x-axis coordinate where the chess piece is located 
    private Double positionX;
     //The y-axis coordinate where the chess piece is located 
    private Double positionY;

    public ChessPiece(ChessPieceUnit chessPieceUnit, Double positionX, Double positionY) {
        this.chessPieceUnit = chessPieceUnit;
        this.positionX = positionX;
        this.positionY = positionY;
    }

6.3 Differences

Difference between flyweight and singleton object

A singleton object is a class that can only create one object, and a flyweight object can create multiple objects. The flyweight object can be regarded as a variant of the singleton object: the reuse of multiple (limited) objects.

The difference between flyweight and object pool

All are reuse of objects, but the object pool is more focused on the user’s exclusive use of the object, and put it back into the pool for other objects to use after use. The Flyweight model focuses on the common use of multiple users

6.4 Applications in the framework

​ Integer and Long in jdk use flyweight mode to cache some data.

String constant pool in jdk

7 Summary of Structural Design Patterns

Design Patterns effect
proxy mode The proxy mode defines a proxy class for the original class without changing the interface of the original class. The main purpose is to control access, not to enhance the function. This is the biggest difference between it and the decorator mode.
bridge mode The purpose of the bridge pattern is to separate the interface part from the implementation part so that they can be changed relatively easily and independently.
adapter mode The adapter pattern is an afterthought remediation strategy. The adapter provides a different interface from the original class, while the proxy mode and the decorator mode provide the same interface as the original class
Decorator pattern The decorator pattern enhances the functionality of the original class without changing the interface of the original class, and supports the nested use of multiple decorators.
facade pattern Simplify the complexity of interface calls in the case of fine-grained interfaces
Combination mode More like an abstraction of data structures and algorithms for business scenarios
flyweight pattern “Flyweight”, as the name suggests, is a unit that is shared. The purpose of the flyweight pattern is to reuse objects and save memory

Leave a Comment

Your email address will not be published. Required fields are marked *