“This is my 23rd day of the August Genwen Challenge.More challenges in August”

What is bridge mode?

Bridge Pattern, also known as Bridge Pattern, is to separate the abstract part from its concrete implementation part, so that they can be independently changed, belonging to the structural Pattern.

The bridge pattern primarily establishes a connection between two classes through composition rather than inheritance. But it is similar to the multiple inheritance scheme, but the multiple inheritance scheme often violates the principle of single responsibility of class, and its reusability is poor. Bridge mode is a better alternative than multiple inheritance scheme. The core of the bridge pattern lies in decoupling abstractions and implementations.

2. Application scenarios of bridge mode

When there are two or more varying dimensions within a class, using the bridge pattern can decouple these varying dimensions and stabilize the high-level code architecture. The bridge mode applies to the following service scenarios:

  1. Scenarios that require more flexibility between abstraction and concrete implementation.
  2. A class has two (or more) dimensions that vary independently, and both dimensions need to be extended independently.
  3. You don’t want to use inheritance, or the number of system classes increases dramatically because of multiple levels of inheritance.

A common use scenario for the bridge pattern is to replace inheritance. As we know, inheritance has many advantages, such as abstraction, encapsulation, polymorphism, etc., the parent class encapsulates the common, and the child class implements the characteristics. Inheritance is a great way to reuse code (encapsulation), but at the same time, it is a big disadvantage of inheritance. Because the parent class owns methods, the child class inherits them, whether the child class needs them or not, which shows that inheritance is very intrusive (the parent class code invents the child class), and also makes the child class bloated… Therefore, in design patterns, it is a principle to use composition/aggregation in preference to inheritance.

Bridge mode can also be seen everywhere in life scenes, such as the bridge connecting two spatial dimensions, such as the connection between virtual network and real network.

Application of bridge mode in business scenarios

For example, when we are in the office, we often communicate with our colleagues by email message, SMS message or system message. Especially when we go through some approval processes, we need to document those processes for future reference. According to the types of messages, we can divide them into email messages, SMS messages and intra-system messages. However, according to the degree of urgency, messages can be classified into ordinary messages, urgent messages and urgent messages. Obviously, the entire message system can be divided into two dimensions, as shown below:

If we use inheritance, the situation is complicated and not conducive to scaling. Email messages can be regular or urgent, and text messages can be regular or urgent. Let’s use bridge mode to solve this problem:

First create an IMessage interface to act as a bridge:

/** * implements a unified interface for sending messages */
public interface IMessage {

    // The content of the message to be sent and the recipient
    void send(String message, String toUser);
}
Copy the code

Create a mail message implementation EmailMessage class:

/** * Mail short message implementation class */
public class EmailMessage implements IMessage {
    @Override
    public void send(String message, String toUser) {
        System.out.println(String.format("Send message %s to %s using email SMS.", message, toUser)); }}Copy the code

Create SmsMessage class:

/** * System Short message implementation class * SMS(Short IMessage Service) */
public class SmsMessage implements IMessage {
    @Override
    public void send(String message, String toUser) {
        System.out.println(String.format("Send message %s to %s using system internal short message method", message, toUser)); }}Copy the code

Next, create the bridge abstract role AbstractMessage class:

/** * Abstract class message */
public abstract class AbstractMessage {

    // Hold an abstraction of the implementation part
    private IMessage iMessage;

    // Constructor, passing in the object of the implementation part
    public AbstractMessage(IMessage iMessage) {
        this.iMessage = iMessage;
    }

    // Send the message, delegate to the implementation part of the method
    public void sendMessage(String message, String toUser) { iMessage.send(message, toUser); }}Copy the code

Create a NormalMessage class that implements a NormalMessage:

/** * Ordinary message class */
public class NormalMessage extends AbstractMessage {

    // Constructor, passing in the object of the implementation part
    public NormalMessage(IMessage iMessage) {
        super(iMessage);
    }

    @Override
    public void sendMessage(String message, String toUser) {
        // For normal messages, call the parent method directly and send the message
        super.sendMessage(message, toUser); }}Copy the code

Create the UrgencyMessage class that implements the emergency message:

/** * urgent message */
public class UrgencyMessage extends AbstractMessage {

    // constructor
    public UrgencyMessage(IMessage iMessage) {
        super(iMessage);
    }

    @Override
    public void sendMessage(String message, String toUser) {
        message = "Urgent:" + message;
        super.sendMessage(message, toUser); }}Copy the code

Write client-side test code:

public class Test {

    public static void main(String[] args) {
        IMessage message = new SmsMessage();
        AbstractMessage abstractMessage = new NormalMessage(message);
        abstractMessage.sendMessage("Special Approval for Overtime Application"."Ma Zong");

        message = new EmailMessage();
        abstractMessage = new UrgencyMessage(message);
        abstractMessage.sendMessage("Special Approval for Overtime Application"."Ma Zong"); }}Copy the code

The running results are as follows:

In the above case, we use the bridge mode to decouple the two independent change dimensions of “message type” and “message urgency degree”. If there are more message types, such as wechat and Dingding, then we can directly create a new class to inherit IMessage. If the demand for urgency degree increases, Again, you just need to create a new class that implements the AbstractMessage class.

Four, bridge mode in the source code application

You are familiar with the JDBC API, and one of the Driver classes is the bridge object. As we all know, Driver classes implemented by various database vendors can be dynamically loaded with the class.forname () method when we use it. The specific client application code is as follows, using MySQL as an example:

public class Test {
    
    public static void main(String[] args) throws Exception {
        //1. Load the driver
        Class.forName("com.mysql.jdbc.Driver");// Reflection loads the driver class
        //2. Obtain the Connection
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test"."root"."root");
        //3. Get the Statement object to execute the SQL Statement
        Statement stmt = conn.createStatement();
        //4. Execute the SQL statement and return the result
        ResultSet rs = stmt.executeQuery("select * from xxx"); }}Copy the code

First, let’s look at the definition of the Driver interface:

public interface Driver {

    Connection connect(String url, java.util.Properties info)
        throws SQLException;

    boolean acceptsURL(String url) throws SQLException;

    DriverPropertyInfo[] getPropertyInfo(String url, java.util.Properties info)
                         throws SQLException;

    int getMajorVersion(a);

    int getMinorVersion(a);

    boolean jdbcCompliant(a);

    public Logger getParentLogger(a) throws SQLFeatureNotSupportedException;
}
Copy the code

Driver does not do any implementation in JDBC. Specific functions are implemented by various vendors. We take MySQL as an example.

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    //
    // Register ourselves with the DriverManager
    //
    static {
        try {
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!"); }}/**
     * Construct a new driver and register it with DriverManager
     *
     * @throws SQLException
     * if a database error occurs.
     */
    public Driver(a) throws SQLException {
        // Required for Class.forName().newInstance()}}Copy the code

When we execute the class.forname (” com.mysql.jdbc.driver “) method, we execute the code in the static code block of the com.mysql.jdbc.driver Class. The static code simply calls the registerDriver() method of DriverManager and registers the Driver object with DriverManager. We can follow up to the DriverManager class to see the code:

public class DriverManager {


    // List of registered JDBC drivers
    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
    private static volatile int loginTimeout = 0;
    private static volatile java.io.PrintWriter logWriter = null;
    private static volatile java.io.PrintStream logStream = null;
    // Used in println() to synchronize logWriter
    private final static  Object logSync = new Object();

    /* Prevent the DriverManager class from being instantiated. */
    private DriverManager(a){}

    static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }

    final static SQLPermission SET_LOG_PERMISSION =
        new SQLPermission("setLog");

    final static SQLPermission DEREGISTER_DRIVER_PERMISSION =
        new SQLPermission("deregisterDriver");

    public static java.io.PrintWriter getLogWriter(a) {
            return logWriter;
    }

    public static void setLogWriter(java.io.PrintWriter out) {

        SecurityManager sec = System.getSecurityManager();
        if(sec ! =null) {
            sec.checkPermission(SET_LOG_PERMISSION);
        }
            logStream = null;
            logWriter = out;
    }

    @CallerSensitive
    public static Connection getConnection(String url, java.util.Properties info) throws SQLException {

        return (getConnection(url, info, Reflection.getCallerClass()));
    }

    @CallerSensitive
    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()));
    }

    @CallerSensitive
    public static Connection getConnection(String url)
        throws SQLException {

        java.util.Properties info = new java.util.Properties();
        return (getConnection(url, info, Reflection.getCallerClass()));
    }

    @CallerSensitive
    public static Driver getDriver(String url)
        throws SQLException {

        println("DriverManager.getDriver(\"" + url + "\")"); Class<? > callerClass = Reflection.getCallerClass();// Walk through the loaded registeredDrivers attempting to locate someone
        // who understands the given URL.
        for (DriverInfo aDriver : registeredDrivers) {
            // If the caller does not have permission to load the driver then
            // skip it.
            if(isDriverAllowed(aDriver.driver, callerClass)) {
                try {
                    if(aDriver.driver.acceptsURL(url)) {
                        // Success!
                        println("getDriver returning " + aDriver.driver.getClass().getName());
                    return(aDriver.driver); }}catch(SQLException sqe) {
                    // Drop through and try the next driver.}}else {
                println(" skipping: " + aDriver.driver.getClass().getName());
            }

        }

        println("getDriver: no suitable driver");
        throw new SQLException("No suitable driver"."08001");
    }

    public static synchronized void registerDriver(java.sql.Driver driver)
        throws SQLException {

        registerDriver(driver, null);
    }

    public static synchronized void registerDriver(java.sql.Driver driver, DriverAction da)
        throws SQLException {

        /* Register the driver if it has not already been added to our list */
        if(driver ! =null) {
            registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
        } else {
            // This is for compatibility with the original DriverManager
            throw new NullPointerException();
        }

        println("registerDriver: " + driver);

    }

    @CallerSensitive
    public static synchronized void deregisterDriver(Driver driver)
        throws SQLException {
        if (driver == null) {
            return;
        }

        SecurityManager sec = System.getSecurityManager();
        if(sec ! =null) {
            sec.checkPermission(DEREGISTER_DRIVER_PERMISSION);
        }

        println("DriverManager.deregisterDriver: " + driver);

        DriverInfo aDriver = new DriverInfo(driver, null);
        if(registeredDrivers.contains(aDriver)) {
            if (isDriverAllowed(driver, Reflection.getCallerClass())) {
                DriverInfo di = registeredDrivers.get(registeredDrivers.indexOf(aDriver));
                 // If a DriverAction was specified, Call it to notify the
                 // driver that it has been deregistered
                 if(di.action() ! =null) {
                     di.action().deregister();
                 }
                 registeredDrivers.remove(aDriver);
            } else {
                // If the caller does not have permission to load the driver then
                // throw a SecurityException.
                throw newSecurityException(); }}else {
            println(" couldn't find driver to unload"); }}@CallerSensitive
    public static java.util.Enumeration<Driver> getDrivers(a) {
        java.util.Vector<Driver> result = newjava.util.Vector<>(); Class<? > callerClass = Reflection.getCallerClass();// Walk through the loaded registeredDrivers.
        for(DriverInfo aDriver : registeredDrivers) {
            // If the caller does not have permission to load the driver then
            // skip it.
            if(isDriverAllowed(aDriver.driver, callerClass)) {
                result.addElement(aDriver.driver);
            } else {
                println(" skipping: "+ aDriver.getClass().getName()); }}return (result.elements());
    }

    public static void setLoginTimeout(int seconds) {
        loginTimeout = seconds;
    }

    public static int getLoginTimeout(a) {
        return (loginTimeout);
    }

    public static void println(String message) {
        synchronized (logSync) {
            if(logWriter ! =null) {
                logWriter.println(message);

                // automatic flushing is never enabled, so we must do it ourselveslogWriter.flush(); }}}private static boolean isDriverAllowed(Driver driver, Class
        caller) { ClassLoader callerCL = caller ! =null ? caller.getClassLoader() : null;
        return isDriverAllowed(driver, callerCL);
    }

    private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) {
        boolean result = false;
        if(driver ! =null) { Class<? > aClass =null;
            try {
                aClass = Class.forName(driver.getClass().getName(), true, classLoader);
            } catch (Exception ex) {
                result = false;
            }

             result = ( aClass == driver.getClass() ) ? true : false;
        }

        return result;
    }

    private static void loadInitialDrivers(a) {
        String drivers;
        try {
            drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
                public String run(a) {
                    return System.getProperty("jdbc.drivers"); }}); }catch (Exception ex) {
            drivers = null;
        }

        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run(a) {

                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();

               
                try{
                    while(driversIterator.hasNext()) { driversIterator.next(); }}catch(Throwable t) {
                // Do nothing
                }
                return null; }}); println("DriverManager.initialize: jdbc.drivers = " + drivers);

        if (drivers == null || drivers.equals("")) {
            return;
        }
        String[] driversList = drivers.split(":");
        println("number of Drivers:" + driversList.length);
        for (String aDriver : driversList) {
            try {
                println("DriverManager.Initialize: loading " + aDriver);
                Class.forName(aDriver, true,
                        ClassLoader.getSystemClassLoader());
            } catch (Exception ex) {
                println("DriverManager.Initialize: load failed: "+ ex); }}}// Worker method called by the public getConnection() methods.
    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"); }}class DriverInfo {

    final Driver driver;
    DriverAction da;
    DriverInfo(Driver driver, DriverAction action) {
        this.driver = driver;
        da = action;
    }

    @Override
    public boolean equals(Object other) {
        return (other instanceof DriverInfo)
                && this.driver == ((DriverInfo) other).driver;
    }

    @Override
    public int hashCode(a) {
        return driver.hashCode();
    }

    @Override
    public String toString(a) {
        return ("driver[className="  + driver + "]");
    }

    DriverAction action(a) {
        returnda; }}Copy the code

Before registration, encapsulate the Driver object as a DriverInfo object. Proceed to step 2 of the client code by calling the getConnection() method of DriverManager to get the connection object. In getConnection(), the connect() method of the Driver implemented by the respective vendor is called to retrieve the connection object. In this way, inheritance is cleverly avoided and the same interface is provided for different databases. DriverManager is the bridge in the JDBC API, as shown below:

Advantages and disadvantages of bridge mode

Advantages:

  1. Separate the abstract part from the concrete implementation part.
  2. It provides system expansibility.
  3. In line with the open and close principle.
  4. In line with the principle of composite reuse.

Disadvantages:

  1. It is difficult to understand and design the system.
  2. It is necessary to correctly identify the two independently varying dimensions of the system.

Six, friendship links

Design Patterns – Factory Patterns learning tour

Design Patterns – a learning tour of singleton patterns

Design Patterns – A learning journey of prototyping patterns

Design Patterns – Builder patterns learning tour

Design Patterns – Agent patterns learning tour

Design Patterns – A learning tour of facade patterns

Design Patterns – A learning tour of decorator patterns

Design Patterns – Enjoy yuan patterns learning journey

Design Patterns – A learning journey of composite patterns

Design Patterns – Adapter patterns learning journey

Welcome to follow the wechat public account (MarkZoe) to learn from and communicate with each other.