Architect's internal mental skill, a detailed explanation of bridging mode connecting two spatial dimensions

In real life, the bridge mode can also be seen everywhere, such as connecting two spatial dimensions of the bridge, connecting the virtual network and the real network.

Bridge Pattern is also called Bridge Pattern, interface pattern or Handle And Body pattern. It separates the abstract part from its concrete implementation part so that they can change independently.

1, Application scenario of bridge mode

The main purpose of the bridging pattern is to establish the relationship between two classes through combination, rather than inheritance. But it is similar to the multiple inheritance scheme, but the multiple inheritance scheme violates the single responsibility principle of the class, and its reusability is relatively poor. The bridge mode is a better alternative to the multiple inheritance scheme. The core of bridging pattern is to decouple abstraction and implementation.

1.1 role of bridge mode

Next, let's look at the common UML diagram of bridging mode:

From the UML diagram, we can see that the bridging pattern mainly includes four roles:

  • Abstract: this class holds a reference to the implementation role. Methods in the abstract role need to be implemented by the implementation role. Abstract roles are generally abstract classes (constructors specify that subclasses pass in an implementation object);
  • Modified abstraction: concrete implementation of abstraction, extending and improving abstract class methods.
  • Implementation: determine the basic operation of the implementation dimension and provide it to the abstract class. This class is generally an abstract class or interface.
  • Concrete Implementor: the concrete implementation of the implementation class.

Here is the specific implementation code:

First, create the abstract Abstraction class:

public abstract class Abstraction {

    protected IImplementor iImplementor;

    public Abstraction(IImplementor iImplementor) {
        this.iImplementor = iImplementor;
    }

    void operation(){
        this.iImplementor.operationImpl();
    }

}

Create the revisionabstractrefinedabstraction class:

public class RefinedAbstraction extends Abstraction {

    public RefinedAbstraction(IImplementor iImplementor) {
        super(iImplementor);
    }

    @Override
    void operation(){
        super.operation();
        System.out.println("refined operation");
    }

}

Create a role to implement the IImplementor interface:

public interface IImplementor {

    void operationImpl();
}

Create concrete implementation classes ConcreteImplementorA and ConcreteImplementorB:

public class ConcreteImplementorA implements IImplementor {
    @Override
    public void operationImpl() {
        System.out.println("concreteImplementor A");
    }
}
public class ConcreteImplementorB implements IImplementor {
    @Override
    public void operationImpl() {
        System.out.println("concreteImplementor B");
    }
}

Test the main method:

   public static void main(String[] args) {
    
    IImplementor iImplementorA = new ConcreteImplementorA();
    IImplementor iImplementorB = new ConcreteImplementorB();

    Abstraction absA = new RefinedAbstraction(iImplementorA);
    Abstraction absB = new RefinedAbstraction(iImplementorB);

    absA.operation();
    absB.operation();
}

The bridge mode has the following application scenarios:

  • More flexible scenarios need to be added between abstract and concrete implementation;
  • A class has two (or more) independent changing dimensions, which need to be extended independently;
  • You do not want to use inheritance, or the number of system classes increases dramatically due to multi-level inheritance.

1.2 business implementation case of bridge mode

We often need to communicate with our colleagues by sending email messages, nailing messages or messages in the system when we work at ordinary times. Especially when some processes are used for approval, we need to record these processes for future reference. It can be divided into mail message, pin message and system message according to the type of message. However, if it is divided according to the urgency of the message, it can be divided into ordinary message, emergency message and emergency message. Obviously, the whole message system can be divided into two dimensions.

If inheritance is used, the situation is complex and not conducive to expansion. E-mail messages can be normal or urgent; PIN messages can be normal or urgent. Use the bridge mode to solve these problems:

First, create an IMessage interface to act as a bridge:

/**
 * Unified interface for message sending
 */
public interface IMessage {

    /**
     * send message
     * @param message
     *                  content
     * @param user
     *                  Receiver
     */
   public void send(String message, String user);
}

Create a mail message class to implement IMessage interface:

/**
 * Mail information implementation class
 */
public class EmailMessage implements IMessage {
    @Override
    public void send(String message, String user) {
        System.out.println(String.format("Send messages by mail %s to %s", message, user));
    }
}

Creating a pin message class also implements the IMessage interface:

/**
 * Nail information implementation class
 */
public class DingMessage implements IMessage {
    @Override
    public void send(String message, String user) {
        System.out.println(String.format("Send message by pin %s to %s", message, user));
    }
}

Then create the abstract role AbstractMessage class,

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

    //Implementation objects
    IMessage message;

    //The object of the implementation part passed in by the construction method
    public AbstractMessage(IMessage message) {
        this.message = message;
    }

    /**
     * Sending messages, delegating to implementation objects
     * @param message
     * @param user
     */
    public void sendMessage(String message, String user) {
        this.message.send(message, user);
    }
}

Create a specific ordinary message to implement the NormalMessage class:

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

    //Construction method passed in the implemented object
    public NormalMessage(IMessage message) {
        super(message);
    }

    /**
     * Send a message and call the method of the parent class directly
     * @param message
     * @param user
     */
    public void sendMessage(String message, String user) {
        super.sendMessage(message, user);
    }
}

Create a specific emergency message to implement the UrgencyMessage class:

/**
 * Emergency message class
 */
public class UngencyMessage extends AbstractMessage {
    public UngencyMessage(IMessage message) {
        super(message);
    }

    /**
     * Send a message and call the method of the parent class directly
     * @param message
     * @param user
     */
    public void sendMessage(String message, String user) {
        super.sendMessage(message, user);
    }

    /**
     * Expand their own functions and monitor the status of messages
     * @param messageId
     * @return
     */
    public Object watch(String messageId) {
        return null;
    }
}

Test the main method:

public static void main(String[] args) {

    IMessage message = new EmailMessage();
    AbstractMessage abstractMessage = new NormalMessage(message);
    abstractMessage.sendMessage("Weekend overtime application", "Zhang San");

    message = new DingMessage();
    abstractMessage = new UngencyMessage(message);
    abstractMessage.sendMessage("Leave request", "Li Si");
}

Operation result:

In the above case, we use the bridging mode to decouple the two independent changing dimensions of "message type" and "message urgency". If you need to extend the contents of these two dimensions, just extend them in the way of the above code.

2, Source code embodiment of bridge mode

Driver class in JDBC

We are all very familiar with JDBC API, one of which is the bridge class. When using, Driver classes implemented by various database vendors can be loaded dynamically through Class.forName() method. Take mysql client as an example:

private Vector<Connection> pool;
private String url = "jdbc:mysql://localhost:3306/testDB";
private String username = "root";
private String password = "123456";
private String driverClassName = "com.mysql.jdbc.Driver";
private int poolSize = 100;
public ConnectionPool() {
    pool = new Vector<Connection>(poolSize);
    try{
        Class.forName(driverClassName);
        for (int i = 0; i < poolSize; i++) {
            Connection conn = DriverManager.getConnection(url,username,password);
            pool.add(conn);
        }
    }catch (Exception e){
        e.printStackTrace();
    }
}

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

There is no implementation of Driver in JDBC. The specific function implementation is completed by various manufacturers. We take Mysql as an example:

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

When we execute the Class.forName("com.mysql.jdbc.Driver") method, we execute the code in the static block of the above class. The static block just calls the registerDriver() method of DriverManager, and then registers the driver object into DriverManager. Next, take a look at the relevant source code in DriverManager:

/**
     * Registers the given driver with the {@code DriverManager}.
     * A newly-loaded driver class should call
     * the method {@code registerDriver} to make itself
     * known to the {@code DriverManager}. If the driver is currently
     * registered, no action is taken.
     *
     * @param driver the new JDBC Driver that is to be registered with the
     *               {@code DriverManager}
     * @exception SQLException if a database access error occurs
     * @exception NullPointerException if {@code driver} is null
     */
    public static synchronized void registerDriver(java.sql.Driver driver)
        throws SQLException {

        registerDriver(driver, null);
    }

    /**
     * Registers the given driver with the {@code DriverManager}.
     * A newly-loaded driver class should call
     * the method {@code registerDriver} to make itself
     * known to the {@code DriverManager}. If the driver is currently
     * registered, no action is taken.
     *
     * @param driver the new JDBC Driver that is to be registered with the
     *               {@code DriverManager}
     * @param da     the {@code DriverAction} implementation to be used when
     *               {@code DriverManager#deregisterDriver} is called
     * @exception SQLException if a database access error occurs
     * @exception NullPointerException if {@code driver} is null
     * @since 1.8
     */
    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);

    }

Before registration, the passed Driver object is encapsulated as a DriverInfo object. Next, call the getConnection() method in DriverManager to get the connection object. See the source code below:

/**
     * Attempts to establish a connection to the given database URL.
     * The <code>DriverManager</code> attempts to select an appropriate driver from
     * the set of registered JDBC drivers.
     *<p>
     * <B>Note:</B> If a property is specified as part of the {@code url} and
     * is also specified in the {@code Properties} object, it is
     * implementation-defined as to which value will take precedence.
     * For maximum portability, an application should only specify a
     * property once.
     *
     * @param url a database url of the form
     * <code> jdbc:<em>subprotocol</em>:<em>subname</em></code>
     * @param info a list of arbitrary string tag/value pairs as
     * connection arguments; normally at least a "user" and
     * "password" property should be included
     * @return a Connection to the URL
     * @exception SQLException if a database access error occurs or the url is
     * {@code null}
     * @throws SQLTimeoutException  when the driver has determined that the
     * timeout value specified by the {@code setLoginTimeout} method
     * has been exceeded and has at least tried to cancel the
     * current database connection attempt
     */
    @CallerSensitive
    public static Connection getConnection(String url,
        java.util.Properties info) throws SQLException {

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

    /**
     * Attempts to establish a connection to the given database URL.
     * The <code>DriverManager</code> attempts to select an appropriate driver from
     * the set of registered JDBC drivers.
     *<p>
     * <B>Note:</B> If the {@code user} or {@code password} property are
     * also specified as part of the {@code url}, it is
     * implementation-defined as to which value will take precedence.
     * For maximum portability, an application should only specify a
     * property once.
     *
     * @param url a database url of the form
     * <code>jdbc:<em>subprotocol</em>:<em>subname</em></code>
     * @param user the database user on whose behalf the connection is being
     *   made
     * @param password the user's password
     * @return a connection to the URL
     * @exception SQLException if a database access error occurs or the url is
     * {@code null}
     * @throws SQLTimeoutException  when the driver has determined that the
     * timeout value specified by the {@code setLoginTimeout} method
     * has been exceeded and has at least tried to cancel the
     * current database connection attempt
     */
    @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()));
    }

    /**
     * Attempts to establish a connection to the given database URL.
     * The <code>DriverManager</code> attempts to select an appropriate driver from
     * the set of registered JDBC drivers.
     *
     * @param url a database url of the form
     *  <code> jdbc:<em>subprotocol</em>:<em>subname</em></code>
     * @return a connection to the URL
     * @exception SQLException if a database access error occurs or the url is
     * {@code null}
     * @throws SQLTimeoutException  when the driver has determined that the
     * timeout value specified by the {@code setLoginTimeout} method
     * has been exceeded and has at least tried to cancel the
     * current database connection attempt
     */
    @CallerSensitive
    public static Connection getConnection(String url)
        throws SQLException {

        java.util.Properties info = new java.util.Properties();
        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");
    }

In getConnection(), the Connect() method of the Driver implemented by the respective manufacturer will be called to obtain the connection object. This skilfully avoids using inheritance and provides the same interface for different databases.

3, Advantages and disadvantages of bridge mode

The bridge mode follows the principle of Richter's substitution and dependency inversion, and finally realizes the principle of opening and closing, closing for modification, and developing for extension. The advantages and disadvantages are summarized as follows:

Advantage:

  • Separating the abstract part and its concrete realization part;
  • Improve the expansibility of the system;
  • Comply with the opening and closing principle;
  • It conforms to the principle of synthesis.

Disadvantages:

  • Increase the difficulty of system design and understanding;
  • It is necessary to correctly identify two independent changing dimensions in the system

Tags: Programming Database JDBC Java MySQL

Posted on Wed, 11 Mar 2020 02:29:07 -0700 by belayet