[design mode 6] bridging mode

Posted by ricoche on Fri, 21 Jan 2022 20:51:12 +0100

Basic introduction

  1. Bridge pattern refers to putting the implementation and abstraction in two different class levels, so that the two levels can be changed independently.
  2. It is a structural design mode.
  3. Bridge pattern is based on the minimum design principle of classes. Different classes assume different responsibilities by using encapsulation, aggregation and inheritance. Its main feature is to separate abstraction from implementation, so as to maintain the independence of each part and deal with their function expansion.

Intent: separate the abstract part from the implementation part so that they can change independently.
Main solution: when there are many possible changes, using inheritance will cause class explosion and inflexible expansion.
When to use: the implementation system may have multiple angle classifications, and each angle may change.
How to solve it: separate the multi angle classification, let them change independently and reduce the coupling between them.
Key code: abstract classes depend on implementation classes.

Pattern 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.
Client: Caller

Bridge mode implementation code

//caller 
public class BridgeClient {
    public static void main(String[] args) {
        Implementor imple = new ConcreteImplementorA();
        Abstraction abs = new RefinedAbstraction(imple);
        abs.Operation();
    }
}
//Realize role
interface Implementor {
    public void OperationImpl();
}
//Concrete implementation role
class ConcreteImplementorA implements Implementor {
    public void OperationImpl() {
        System.out.println("Concrete realization(Concrete Implementor)Role is accessed");
    }
}
//Abstract role
abstract class Abstraction {
    protected Implementor imple;
    protected Abstraction(Implementor imple) {
        this.imple = imple;
    }
    public abstract void Operation();
}
//Extended abstract role
class RefinedAbstraction extends Abstraction {
    protected RefinedAbstraction(Implementor imple) {
        super(imple);
    }
    public void Operation() {
        System.out.println("Extended abstraction(Refined Abstraction)Role is accessed");
        imple.OperationImpl();
    }
}

Precautions and details of bridging mode

advantage

  1. It realizes the separation of abstract part and implementation part, which greatly provides the flexibility of the system and makes the abstract part and implementation part independent, which is helpful for the hierarchical design of the system, so as to produce a better structured system.
  2. For the high-level part of the system, you only need to know the interface between the abstract part and the implementation part, and the other parts are completed by the specific business.
  3. Bridging mode replaces multi-layer inheritance scheme, which can reduce the number of subclasses and reduce the management and maintenance cost of the system.

shortcoming

  1. The introduction of bridging mode increases the difficulty of system understanding and design. Because the aggregation association relationship is based on the abstraction layer, developers are required to design and program for the abstraction
  2. The bridging mode requires to correctly identify two independently changing dimensions in the system, so its scope of use has certain limitations, that is, it needs such an application scenario.

Usage scenario

  • 1. If a system needs to add more flexibility between the abstract role and concrete role of components, avoid establishing static inheritance relationship between the two levels, and enable them to establish an association relationship at the abstract level through bridging mode.

  • 2. For those systems that do not want to use inheritance or the number of system classes increases sharply due to multi-level inheritance, the bridging mode is particularly applicable.

  • 3. A class has two independently changing dimensions, and both dimensions need to be extended. For two independently changing dimensions, the bridge pattern is perfect.

Common application scenarios:

  • -JDBC Driver

  • -Bank transfer system

    Transfer classification: online transfer, counter transfer, AMT transfer
    Transfer user type: ordinary user, silver card user, gold card user

  • -Message management

    Message type: instant message, delayed message
    Message classification: SMS, email, QQ

java sample code

Bridging mode solves the problems of mobile phone brand and mobile phone style
UML class diagram

1. Brand:
//Mobile phone brand

public interface PhoneBrand {

    //Mobile phone function
    void open();

    void close();

    void call();
}

2. Specific implementation:

public class HuaWeiPhone implements PhoneBrand{
    @Override
    public void open() {
        System.out.println("hua wei phone open");
    }

    @Override
    public void close() {
        System.out.println("hua wei phone close");
    }

    @Override
    public void call() {
        System.out.println("hua wei phone calling");
    }
}

public class XiaoMiPhone implements PhoneBrand {
    @Override
    public void open() {
        System.out.println("xiao mi phone open");
    }

    @Override
    public void close() {
        System.out.println("xiao mi phone close");
    }

    @Override
    public void call() {
        System.out.println("xiao mi pone calling");
    }
}

3. Abstract class: mobile phone

//Abstract class, mobile phone
public abstract class Phone {
    //Combined brand
    private PhoneBrand brand;

    //constructor 
    public Phone(PhoneBrand brand) {
        super();
        this.brand = brand;
    }
    
    //Mobile phone function
    protected void open() {
        this.brand.open();
    }

    protected void close() {
        brand.close();
    }

    protected void call() {
        brand.call();
    }

}

4. Abstract implementation

//Upright mobile phone
public class FoldPhone extends Phone {

    public FoldPhone(PhoneBrand brand) {
        super(brand);
    }

    public void open() {
        super.open();
    }

    public void close() {
        super.close();
    }

    public void call() {
        super.call();
    }

}

//Full screen mobile phone
public class FullScreenPhone extends Phone{

    public FullScreenPhone(PhoneBrand brand) {
        super(brand);
    }

    public void open(){
        super.open();
    }

    public void close(){
        super.close();
    }

    public void call(){
        super.call();
    }

}

5. Call:

public class Client {

    public static void main(String[] args) {

        //Get a folding phone (style + brand)

        Phone phone1 = new FoldPhone(new XiaoMiPhone());

        phone1.open();
        phone1.call();
        phone1.close();

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

        Phone phone2 = new FoldPhone(new HuaWeiPhone());

        phone2.open();
        phone2.call();
        phone2.close();

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

        Phone phone3 = new FullScreenPhone(new XiaoMiPhone());

        phone3.open();
        phone3.call();
        phone3.close();

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

        Phone phone4 = new FullScreenPhone(new HuaWeiPhone());

        phone4.open();
        phone4.call();
        phone4.close();
    }
}

Application of bridge mode in JDBC source code

We must be familiar with the Driver interface. From the perspective of bridging mode, Driver is an interface. There can be MySQL Driver and Oracle Driver, which can be used as implementation interface classes. Now let's look at the Driver class in MySQL.

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }
 
    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

The particularly short code only calls the registerDriver method in the DriverManager to register the driver. When the driver registration is completed, we will start calling the getConnection method in DriverManager

public class DriverManager {
    public static Connection getConnection(String url,
        String user, String password) throws SQLException {
        java.util.Properties info = new java.util.Properties();
 
        if (user != null) {
            info.put("user", user);
        }
        if (password != null) {
            info.put("password", password);
        }
 
        return (getConnection(url, info, Reflection.getCallerClass()));
    }
 
    private static Connection getConnection(
        String url, java.util.Properties info, Class<?> caller) throws SQLException {
        /*
         * When callerCl is null, we should check the application's
         * (which is invoking this class indirectly)
         * classloader, so that the JDBC driver class outside rt.jar
         * can be loaded from here.
         */
        ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
        synchronized(DriverManager.class) {
            // synchronize loading of the correct classloader.
            if (callerCL == null) {
                callerCL = Thread.currentThread().getContextClassLoader();
            }
        }
 
        if(url == null) {
            throw new SQLException("The url cannot be null", "08001");
        }
 
        println("DriverManager.getConnection(\"" + url + "\")");
 
        // Walk through the loaded registeredDrivers attempting to make a connection.
        // Remember the first exception that gets raised so we can reraise it.
        SQLException reason = null;
 
        for(DriverInfo aDriver : registeredDrivers) {
            // If the caller does not have permission to load the driver then
            // skip it.
            if(isDriverAllowed(aDriver.driver, callerCL)) {
                try {
                    println("    trying " + aDriver.driver.getClass().getName());
                    Connection con = aDriver.driver.connect(url, info);
                    if (con != null) {
                        // Success!
                        println("getConnection returning " + aDriver.driver.getClass().getName());
                        return (con);
                    }
                } catch (SQLException ex) {
                    if (reason == null) {
                        reason = ex;
                    }
                }
 
            } else {
                println("    skipping: " + aDriver.getClass().getName());
            }
 
        }
 
        // if we got here nobody could connect.
        if (reason != null)    {
            println("getConnection failed: " + reason);
            throw reason;
        }
 
        println("getConnection: no suitable driver found for "+ url);
        throw new SQLException("No suitable driver found for "+ url, "08001");
    }
 }
}

The above is the simplified code. You can see that the Connection object needs to be returned. In Java, the same operation interface is provided to each database through Connection, and the Connection here can be regarded as an abstract class. It can be said that the methods we use to operate different databases are the same, but MySQL has its own ConnectionImpl class, and Oracle also has corresponding implementation classes. Here, the Driver and Connection are bridged through the DriverManager class, rather than using the composite relationship as we said above.
As shown in the following UML class diagram

Bridge mode and adapter mode

Differences and relations between adapter mode and bridge mode
Both adapter mode and bridge mode refer to objects indirectly, so it can make the system more flexible. The implementation involves sending requests to referenced objects from an interface other than itself.

The difference between the two modes lies in the different use occasions. The adapter mode mainly solves the matching problem between two existing interfaces. In this case, the implementation of the adapted interface is often a black box. We do not want and cannot modify this interface and its implementation. At the same time, it is impossible to control its evolution, as long as the related objects can work together with the system defined interface. The adapter mode is often used for function integration with third-party products. The way to adapt to the increase of new types is to develop adapters for this type, as shown in the figure below:

The bridging mode is different. The interfaces participating in the bridging are stable. Users can extend and modify the classes in the bridging, but cannot change the interface. The bridge mode implements function extension through interface inheritance or class inheritance. As shown in the figure below:

According to GOF, bridge mode and adapter mode are used in different stages of design, and bridge mode is used in the early stage of design, that is, when designing classes, classes are planned into logic and implementation categories, so that they can evolve carefully respectively; The adapter pattern is used after the design is completed. When it is found that the designed classes cannot work together, the adapter pattern can be adopted. However, in many cases, the use of adapter mode should be considered at the early stage of design, such as involving a large number of third-party application interfaces.

Combination of adapter mode and bridge mode
In practical application, bridge mode and adapter mode often appear at the same time, as shown in the following figure:

This situation often occurs when other systems need to provide implementation methods. A typical example is data acquisition in industrial control. The bottom data acquisition interfaces provided by different industrial control manufacturers are usually different, so it is impossible to predict what interface may be encountered in the upper software design. Therefore, it is necessary to define a general acquisition interface, and then develop the corresponding adapter for the specific data acquisition system. Data storage needs to call the data acquisition interface to obtain data, and the data can be saved to relational database, real-time database or file,. The data storage interface and data acquisition structure are bridged, as shown in the figure below:

The same structure often appears in report related applications. The report structure and report output method can be completely separated, as shown in the following figure:

As shown in the figure above, report output can be abstracted separately from the specific form of the report. However, report output depends on the specific output method. If you need to output in PDF format, you need to call the API related to PDF, which is beyond the control of the design. Therefore, adapter mode is required here.

Topics: Java