23 kinds of design patterns this column allows you to learn so hard that you won't beat me - Structural Model - adapter pattern, bridge pattern, composite pattern

Posted by djdog.us on Sun, 02 Jan 2022 17:43:07 +0100

📖 Content of this article: 23 design patterns -- structural model (I) adapter pattern bridge pattern combination pattern

📆 Last updated: December 26, 2021 23 kinds of design patterns this column makes you learn how to beat me - constructive pattern - factory pattern abstract factory pattern singleton pattern builder pattern prototype pattern

🙊 Personal profile: a Junior Program ape who is studying in a two-year college. As a summary blogger of personal experience, he may be lazy sometimes, but he will stick to it. If you like blog very much, it is suggested to look at the following line ~ (crazy hint QwQ)

🌇 give the thumbs-up 👍 Collection ⭐ Leaving a message. 📝 One button three times love program ape, start from you and me

Write in front

Because 🙊 (APE Xiaofu) recently, the second article on 23 design patterns was published only one week after the examination. The purpose of this article is to get a quick start to understand the 23 design patterns in object-oriented design, so it may not have any special depth, because the author's ability is limited, so he can only give you a general idea, but also make the blogger deepen his impression. If there is any doubt, I hope you can put forward valuable suggestions, study and solve them together, and make progress together ~ don't say much, then go on to the above.

preface

Six principles under 23 design modes

No matter what design mode, it is based on six design principles:

  • Open and closed principle: a software entity such as class, module and function should be closed to modification and open to extension.
  • Single responsibility principle: a class does one thing, and a class should have a reason for its modification.
  • Richter substitution principle: the subclass should be able to completely replace the parent class. In other words, when using inheritance, only extend new functions without destroying the original functions of the parent class.
  • Dependency Inversion Principle: details should depend on abstraction, and abstraction should not depend on details. The abstract layer is placed at the high level of program design and remains stable. The details of the program are changed by the underlying implementation layer.
  • Interface separation principle: the client should not rely on interfaces it does not need. If some methods of an interface are empty implemented by the client due to redundancy during implementation, the interface should be split so that the implementation class only needs the interface methods it needs.
  • Dimitri's Law: the principle of least knowledge to minimize the coupling between classes; One object should have the least understanding of other objects.

Structural model

Adapter mode adapter

What is adapter mode?

Convert the interface of a class into another interface that the customer wants. The Adapter pattern allows classes that cannot work together due to incompatible interfaces to work together.

What problems can it solve?

That is, the Adapter mode enables classes whose original interfaces are incompatible and cannot work together to work together.

Take a small example~

You must have used a real-world card reader. After all, if you want to read the photos in your camera into the computer for corresponding modifications, you need a card reader. Then we usually can't directly insert the SD card into the computer for reading, because ordinary computers seem to have no interface compatible with reading the SD card, but a smart boy invented the card reader, The initial idea of the card reader is derived from the adapter mode. Because the SD card is incompatible with the computer, the card reader is used as an adapter interface to connect the SD card and the USB of the computer. In this way, the data can be successfully read for map repair and other operations.

Roles in patterns?

  • Target interface: the interface expected by the customer. The target can be a concrete or abstract class or an interface.
  • Adaptee: the class to be adapted or the adapter class.
  • Adapter: convert the original interface into the target interface by wrapping an object to be adapted.

How to achieve it?

1. Class (implemented by inheritance)

2. Object adapter (implemented by object combination)

Adapter pattern for class

The SD card needs to be adapted as a USB interface that can read to the computer:

/**
 * Function description
 * Here is an existing standard interface class with special functions, but can not directly read SD card data with USB interface
 * @author Alascanfu
 * @date 2021/12/26
 */
public class SDCard_Adaptee {
    public void specificRequest(){
        System.out.println("display SD Contents of the card");
    }
}

/**
 * Function description
 * The target interface is called the standard interface, which represents the USB interface on the computer
 * @author Alascanfu
 * @date 2021/12/26
 */
public interface USBInterface {
    public void request();
}


/**
 * Function description
 * The ordinary USB interface is only suitable for the tool that accepts the USB interface to read to the disk
 * @author Alascanfu
 * @date 2021/12/26
 */
public class USBTarget implements USBInterface{
    @Override
    public void request() {
        System.out.println("USB Interface with read USB Functions of interface tools");
    }
}

/**
 * Function description
 *  The adapter class inherits the Adaptee class. While reading the SDK, it also implements a standard USB interface that can be inserted into the computer
 * @author Alascanfu
 * @date 2021/12/26
 */
public class CardReader_Adapter extends SDCard_Adaptee implements USBInterface{
    @Override
    public void request() {
        super.specificRequest();
    }
}

public class Test {
    public static void main(String[] args) {
        /**
         *Function description
         * The contents of SD card cannot be read directly using ordinary USB interface
         * @date 2021/12/26
         *  @author Alascanfu
         */
        USBInterface usbTarget = new USBTarget();
        usbTarget.request();
    
        /**
         *Function description
         * The reading interface of the ordinary USB card is realized through the USB adapter
         * This is where an adapter class is compatible with classes that cannot work together
         * @date 2021/12/26
         *  @author Alascanfu
         */
        USBInterface cardReader_adapter = new CardReader_Adapter();
        cardReader_adapter.request();
    }
}

The Adapter implemented above is called class Adapter because the Adapter class inherits the Adaptee (the adapted class) and implements the USBInterface interface (Java does not support multiple inheritance, so it is implemented in this way). In the test class, our customers can choose to create any subclass that meets the requirements according to their needs to realize specific functions. Another Adapter mode is the object Adapter. Instead of using multiple inheritance or inheritance and re implementation, it is directly related, or called a delegated method:

/**
 * Function description
 *  The adapter class is directly associated with the adapted class and implements the USB interface at the same time
 * @author Alascanfu
 * @date 2021/12/26
 */
public class CardReader_Adapter implements USBInterface{
    //Directly related to the adapted class
    private SDCard_Adaptee adaptee ;
    
    // You can pass in the adapted class object that needs to be adapted through the constructor
    public CardReader_Adapter(SDCard_Adaptee adaptee) {
        this.adaptee = adaptee;
    }
    
    @Override
    public void request() {
        //Here, the SD card can be read in the way of entrustment
        this.adaptee.specificRequest();
    }
}


public class Test {
    public static void main(String[] args) {

        /**
         *Function description
         * Using special function classes, i.e. adaptation classes,
         * You need to first create an object of the adapted class as a parameter
         * @date 2021/12/26
         *  @author Alascanfu
         */
        USBInterface cardReader_adapter = new CardReader_Adapter(new SDCard_Adaptee());
        cardReader_adapter.request();
    }
}

The test results are the same. The only difference is that we just modify the internal structure of the Adapter class to have an object of the adapted class, and then delegate the specific special functions to this object. Using the object Adapter mode, you can adapt multiple different adapted classes according to the incoming Adapter object. Of course, at this time, we can extract an interface or abstract class for multiple adapted classes. In this way, it seems that the object Adapter pattern is more flexible.

Actual development case

For example, the adapter (adapter) of mobile phone power supply has been used.

And the Future Task in JDK implements the Runnable interface and combines Callable, which can be combined with Thread.

Bridge mode

Consider a requirement: draw rectangle, prototype, triangle and these three patterns. According to the object-oriented concept, we need at least three specific classes corresponding to three different graphics.

Abstract an interface Shape:

public interface Shape {
    public void draw();
}

Three specific implementation classes:

public class Circle implements Shape{
    @Override
    public void draw() {
        System.out.println("Draw a circle");
    }
}

public class Rectangle implements Shape{
    @Override
    public void draw() {
        System.out.println("Draw a rectangle");
    }
}

public class Triangle implements Shape{
    @Override
    public void draw() {
        System.out.println("Draw a triangle");
    }
}

If new requirements are added at this time, the graphics of each shape need four different colors.

  • In order to reuse shape classes, each shape is defined as a parent class, and each shape with different colors inherits from its shape parent class. At this time, there are 12 classes.
  • In order to reuse color classes, each color is defined as a parent class, and the graphics of each different color inherit from its color parent class. At this time, there are 12 classes.

However, if you need to add a color in the future, you need to add three classes corresponding to three graphics, and add a shape, you need to add five classes corresponding to five colors.

Shape and color are two attributes of graphics. They have an equal relationship and do not belong to inheritance. A better implementation is to separate the shape and color, and combine the shape and color as needed, which is the idea of bridge mode.

Bridging pattern: separate the abstract part from its implementation part. Yes, they can change independently. It is an object structure pattern, also known as handle pattern or interface pattern.

Generally speaking, if an object has two or more classification methods, and both methods are easy to change, such as the shape and color in this example. At this time, the use of inheritance can easily lead to more and more subclasses, so the better way is to separate this classification method and let them change independently. When using, you can combine different classifications.

Let's take a look at the program implementation using bridge mode in this example:

Create a new interface class IColor, which contains only one method to obtain color:

//Interface for bridging
public interface IColor {
    public String getColor();
}

public class Blue implements IColor {
    @Override
    public String getColor() {
        return "blue";
    }
}

public class Red implements IColor{
    @Override
    public String getColor() {
        return "red";
    }
}

Then bridge the interface in each shape:

public class Rectangle implements Shape{
    private IColor color;
    
    void setColor(IColor color){
        this.color = color;
    }
    @Override
    public void draw() {
        System.out.println(color.getColor()+"Draw a rectangle");
    }
}

public class Triangle implements Shape{
    private IColor color;
    
    void setColor(IColor color){
        this.color = color;
    }
    @Override
    public void draw() {
        System.out.println(color.getColor()+"Draw a triangle");
    }
}

public class Circle implements Shape{
    private IColor color;
    
    void setColor(IColor color){
        this.color = color;
    }
    @Override
    public void draw() {
        System.out.println(color.getColor()+"Draw a circle");
    }
}

Test case:

public class Test {
    public static void main(String[] args) {
        Circle circle = new Circle();
        circle.setColor(new Blue());
        circle.draw();
    }
}

The key point of the bridge pattern is to separate the abstract part from its implementation part so that they can change independently. The abstract part refers to the parent class, corresponding to the shape class in this example, and the implementation part refers to the differences between different subclasses. The way to distinguish subclasses - that is, the color in this example - is separated into interfaces, and the color and shape are bridged by combination. This is the bridging mode, which is mainly used for two or more interfaces of the same level.

Composite mode

What is the combination mode? For what?

Composite mode, also known as partial overall mode, is used to treat a group of similar objects as a single object. The composition pattern relies on the tree structure to combine objects, which is used to represent the part and the whole hierarchy. This type of design pattern is a structural pattern because it creates a tree structure of object groups.

The combination mode is used for the structure of the whole and part. When the whole and part have similar structures and can be treated consistently during operation, the combination mode can be used.

  • Relationship between folder and subfolder: files can be stored in a folder or a new folder can be created, and so can subfolders.

  • Relationship between the head office and subsidiaries: the head office can set up departments or branches, and so can subsidiaries.

  • The relationship between branches and branches: branches can grow leaves or branches, and so can branches.

Consider a practical application scenario and design the personnel distribution structure of a company, as shown in the figure:

We notice that there are two kinds of personnel structure: one is the manager, such as the boss, PM, CFO and CTO, and the other is the employee. Some managers not only manage employees, but also manage other managers. This is a typical whole and part structure.

Design without composite mode

Manager class:

package com.alascanfu.CompositePattern;

import java.util.ArrayList;
import java.util.List;

public class Manager {
    //position
    private String position;
    //job content
    private String job;
    //Manager of management
    private List<Manager> managers = new ArrayList<>();
    
    //Managed employees
    private List<Employee> employees = new ArrayList<>();
    
    public Manager(String position, String job) {
        this.position = position;
        this.job = job;
    }
    
    public void addManager(Manager manager){
        managers.add(manager);
    }
    
    public void removeManager(Manager manager){
        managers.remove(manager);
    }
    
    public void addEmployee(Employee employee){
        employees.add(employee);
    }
    public void removeEmployee(Employee employee){
        employees.remove(employee);
    }
    
    public void work(){
        System.out.println("I am"+position+"Is"+job);
    }
    
    // Check subordinates
    public void check() {
        work();
        for (Employee employee : employees) {
            employee.work();
        }
        for (Manager manager : managers) {
            manager.check();
        }
    }
    
}

Employee category:

package com.alascanfu.CompositePattern;

public class Employee {
    // position
    private String position;
    // job content
    private String job;
    
    public Employee(String position, String job) {
        this.position = position;
        this.job = job;
    }
    
    // Do your job
    public void work() {
        System.out.println("I am" + position + ",I'm" + job);
    }

}

Test class:

package com.alascanfu.CompositePattern;

public class Test {
    public static void main(String[] args) {
        Manager boss = new Manager("boss", "Sing the life in full bloom");
        Employee HR = new Employee("human resources", "Chat wechat");
        Manager PM = new Manager("product manager", "I don't know what to do");
        Manager CFO = new Manager("Treasurer", "Watch a play");
        Manager CTO = new Manager("Technical director", "Paddle");
        Employee UI = new Employee("designer", "draw");
        Employee operator = new Employee("Operators", "Part time customer service");
        Employee webProgrammer = new Employee("programmer", "Learning design pattern");
        Employee backgroundProgrammer = new Employee("Back End Programmer ", "CRUD");
        Employee accountant = new Employee("accounting", "Recite 99 multiplication table");
        Employee clerk = new Employee("Clerk", "Pass the microphone to the boss");
        boss.addEmployee(HR);
        boss.addManager(PM);
        boss.addManager(CFO);
        PM.addEmployee(UI);
        PM.addManager(CTO);
        PM.addEmployee(operator);
        CTO.addEmployee(webProgrammer);
        CTO.addEmployee(backgroundProgrammer);
        CFO.addEmployee(accountant);
        CFO.addEmployee(clerk);
    
        boss.check();
    }
}

This structural design has two disadvantages:

  • The name field, job field and work method are repeated.
  • Managers need to treat their managers and employees differently.

How to design the combination mode?

The most important thing of composition mode is to let everyone treat the whole and part structure as one component, perform different work, and abstract the component first.

Transparent way

Component abstract class:

package com.alascanfu.CompositePattern;

public abstract class Component {
    // position
    private String position;
    // job content
    private String job;
    
    public Component(String position, String job) {
        this.position = position;
        this.job = job;
    }
    
    // Do your job
    public void work() {
        System.out.println("I am" + position + ",I'm" + job);
    }
    
    abstract void addComponent(Component component);
    
    abstract void removeComponent(Component component);
    
    abstract void check();
}

The administrator inherits this abstract class:

package com.alascanfu.CompositePattern;

import java.util.ArrayList;
import java.util.List;

public class Manager extends Component{
    // Managed components
    private List<Component> components = new ArrayList<>();
    
    public Manager(String position, String job) {
        super(position, job);
    }
    
    
    @Override
    void addComponent(Component component) {
        components.add(component);
    }
    
    @Override
    void removeComponent(Component component) {
        components.remove(component);
    }
    
    @Override
    void check() {
        work();
        for (Component component : components){
            component.check();
        }
    }
}

Employees also inherit this abstract class:

package com.alascanfu.CompositePattern;

public class Employee extends Component{
    
    public Employee(String position, String job) {
        super(position, job);
    }
    
    @Override
    void addComponent(Component component) {
        System.out.println("The employee has no management authority");
    }
    
    @Override
    void removeComponent(Component component) {
        System.out.println("The employee has no management authority");
    }
    
    @Override
    void check() {
        work();
    }
}

Test code:

package com.alascanfu.CompositePattern;

public class Test {
    public static void main(String[] args) {
        Component boss = new Manager("boss", "Sing the life in full bloom");
        Component HR = new Employee("human resources", "Chat wechat");
        Component PM = new Manager("product manager", "I don't know what to do");
        Component CFO = new Manager("Treasurer", "Watch a play");
        Component CTO = new Manager("Technical director", "Paddle");
        Component UI = new Employee("designer", "draw");
        Component operator = new Employee("Operators", "Part time customer service");
        Component webProgrammer = new Employee("programmer", "Learning design pattern");
        Component backgroundProgrammer = new Employee("Back End Programmer ", "CRUD");
        Component accountant = new Employee("accounting", "Recite 99 multiplication table");
        Component clerk = new Employee("Clerk", "Pass the microphone to the boss");
        boss.addComponent(HR);
        boss.addComponent(PM);
        boss.addComponent(CFO);
        PM.addComponent(UI);
        PM.addComponent(CTO);
        PM.addComponent(operator);
        CTO.addComponent(webProgrammer);
        CTO.addComponent(backgroundProgrammer);
        CFO.addComponent(accountant);
        CFO.addComponent(clerk);
    
        boss.check();
    }
}

It can be seen that after using the composite mode, the previous two shortcomings are solved. First, the public fields and methods are moved to the parent class to eliminate duplication. In the test case, the Manager and Employee classes can be treated in the same way:

  • The Manager class and Employee class uniformly declare the object of Component
  • The addComponent method of the Component object is called uniformly to add child objects

But there are also problems. Do you find that the interface isolation principle is violated in the Employee class!

Interface isolation principle: the client should not rely on interfaces it does not need. If some methods of an interface are empty implemented by the client due to redundancy during implementation, the interface should be split so that the implementation class only needs the interface methods it needs.

Therefore, the improvement in the combination mode - security mode and transparent mode

Transparent way

Transparent method: declare all the methods for managing sub objects in the Component, including add and remove, so that the inherited subclasses have these methods. For the outside world, the leaf node and branch node are transparent and have completely consistent interfaces.

But his shortcomings appear: it violates the principle of interface separation, and when the test case calls the corresponding method, it may lead to program errors, so this method is not safe.

Then we can make improvements: move the two methods that do not need to appear in Employee to be implemented separately in the Manager class, and remove the methods in the abstract class.

Amend as follows:

public abstract class Component {
    // position
    private String position;
    // job content
    private String job;

    public Component(String position, String job) {
        this.position = position;
        this.job = job;
    }

    // Do your job
    public void work() {
        System.out.println("I am" + position + ",I'm" + job);
    }

    abstract void check();
}


Modify the Manager class:

public class Manager extends Component {
    // Managed components
    private List<Component> components = new ArrayList<>();

    public Manager(String position, String job) {
        super(position, job);
    }

    public void addComponent(Component component) {
        components.add(component);
    }

    void removeComponent(Component component) {
        components.remove(component);
    }

    // Check subordinates
    @Override
    public void check() {
        work();
        for (Component component : components) {
            component.check();
        }
    }
}

It separately implements the two methods of adding and removing employees with permission.

The Employee class is modified as follows:

public class Employee extends Component {

    public Employee(String position, String job) {
        super(position, job);
    }

    @Override
    void check() {
        work();
    }
}

These methods are called the security methods of combined mode.

Security method: add, remove and other methods for managing child objects are not declared in the Component, so the leaf node does not need to implement it, but only needs to implement the methods for managing child objects in the branch node.

Question: what is the difference between security and transparency in composite mode?

  • Transparent mode: leaf nodes and branch nodes are transparent to the outside world, and they have completely consistent interfaces.

  • Security method: add, remove and other methods for managing child objects are not declared in the Component, so the leaf node does not need to implement it, but only needs to implement the methods for managing child objects in the branch node.

The key point is: transparent mode and security mode. Transparency violates the principle of interface isolation (that is, all inherit interface methods). The security mode follows the principle of interface isolation.

Write at the end

This is the adapter mode, bridge mode and combination mode in the structural model introduced this time. In the actual development, the adapter mode is also widely used. Developers can design their own adapter mode according to business needs.
Finally, this article refers to the book "23 design patterns in simple terms" on the official website of Likou. For more information, please move to the official website~

last

Make progress every day and harvest every day

May you succeed in your career and learn

If you feel good, don't forget to click three times~

Topics: Java Design Pattern Back-end