一起学设计模式 - 桥接模式

桥接模式(Brideg Pattern)属于结构型模式的一种,用于把抽象化与实现化解耦,使得二者可以独立变化,它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。

<!– more –>

概述

桥接模式是一种很实用的结构型设计模式,如果软件系统中某个类存在两个独立变化的维度,通过该模式可以将这两个维度分离出来,使两者可以独立扩展,让系统更加符合“单一职责原则”。与多层继承方案不同,它将两个独立变化的维度设计为两个独立的继承等级结构,并且在抽象层建立一个抽象关联,该关联关系类似一条连接两个独立继承结构的桥,故名桥接模式。

桥接模式用一种巧妙的方式处理多层继承存在的问题,用抽象关联取代了传统的多层继承,将类之间的静态继承关系转换为动态的对象组合关系,使得系统更加灵活,并易于扩展。

合成复用原则

合成复用原则又称为组合/聚合复用原则(Composition/Aggregate Reuse Principle, CARP),指尽量使用对象组合,而不是继承来达到复用的目的。

为什么要尽量使用合成和聚合,而不用继承

  • 继承复用破坏包装,它把父类的实现细节直接暴露给了子类,父类发生改变,则子类也要发生相应的改变,这就直接导致了类之间的紧耦合,不利于类的扩展、复用、维护。

  • 合成与聚合的时候,对象交互往往是通过接口或抽象类进行的,可以很好的避免上面的不足,每个新的类专注于实现自己的任务,符合单一职责原则。

案例

拿汽车在路上行驶的来说。即有小汽车又有公共汽车,它们都不但能在市区中的公路上行驶,也能在高速公路上行驶。这你会发现,对于交通工具(汽车)有不同的类型,然而它们所行驶的环境(路)也在变化,在软件系统中就要适应两个方面的变化?怎样实现才能应对这种变化呢?

紧耦合示例

《一起学设计模式 - 桥接模式》

1.定义基类Road,它拥有在上面行驶的方法

class Road {
    public void run() {
        System.out.println("在路上");
    }
}

2.路也有多种,这时候咋整?继承一波呗

class SpeedWay extends Road {
    @Override
    public void run() {
        System.out.println("在高速公路上");
    }
}

class Street extends Road {
    @Override
    public void run() {
        System.out.println("在市区街道上");
    }
}

3.问题来了,不同的路跑着不同的车,这个时候针对不同的车都要继承SpeedWay/Street两个类,假设我有(5种车 5条不同的路),妈呀那得多少类,先 (2 2)吧

class CarOnSpeedWay extends SpeedWay {
    @Override
    public void run() {
        System.out.println("汽车在高速公路上行驶");
    }
}

class BusOnSpeedWay extends SpeedWay {
    @Override
    public void run() {
        System.out.println("公共汽车在高速公路上行驶");
    }
}


class CarOnStreet extends Street {
    @Override
    public void run() {
        System.out.println("汽车在街道上行驶");
    }
}

class BusOnStreet extends Street {
    @Override
    public void run() {
        System.out.println("公共汽车在街道上行驶");
    }
}

4.编写测试类,有木有发现代码啰嗦不说,不是说好的一般是面向接口编程么?

public class Client {

    public static void main(String[] args) {
        CarOnSpeedWay carOnSpeedWay = new CarOnSpeedWay();
        carOnSpeedWay.run();

        BusOnSpeedWay busOnSpeedWay = new BusOnSpeedWay();
        busOnSpeedWay.run();

        CarOnStreet carOnStreet = new CarOnStreet();
        carOnStreet.run();

        BusOnStreet busOnStreet = new BusOnStreet();
        busOnStreet.run();
    }
}

弊端: 仔细分析就可以发现,该方案虽然实现了功能但埋了不少坑,它在遵循开放-封闭原则的同时,违背了类的单一职责原则,即一个类只有一个引起它变化的原因,而这里引起变化的原因却有两个,即路类型的变化和汽车类型的变化;其次是重复代码会很多,不同的汽车在不同的路上行驶也会有一部分的代码是相同的;再次是类的结构过于复杂,继承关系太多,难于维护,最后最致命的一点是扩展性太差。如果变化沿着汽车的类型和不同的道路两个方向变化,我们会看到这个类的结构会迅速的变庞大。

松耦合示例

首先我们要搞清楚,路上面可以行驶车辆(包含),但车并不是路的一部分,它们二者并不一定要紧密贴在一块,它们之间是聚合关系

《一起学设计模式 - 桥接模式》

1.定义一个Car的接口,供给不同种类的去实现

interface Car {
    void run();
}

2.抽象类AbstractRoad,与Car关联起来,接收具体的实现

abstract class AbstractRoad {
    protected Car car;

    public void setCar(Car car) {
        this.car = car;
    }
    public abstract void run();
}

3.道路类统一继承AbstractRoad实现抽象方法,汽车类统一实现Car接口

class SpeedWay extends AbstractRoad {
    @Override
    public void run() {
        car.run();
        System.out.println("在高速公路上");
    }
}

class Street extends AbstractRoad {
    @Override
    public void run() {
        car.run();
        System.out.println("在市区街道上");
    }
}

class SmallCar implements Car {
    @Override
    public void run() {
        System.out.println("小汽车");
    }
}
class BigTruck implements Car {
    @Override
    public void run() {
        System.out.println("大卡车");
    }
}

4.编写测试类,通过传入具体实现来获得最终结果

public class Client {

    public static void main(String[] args) {
        Car bigTruck = new BigTruck();
        Car smallCar = new SmallCar();
        AbstractRoad way = new SpeedWay();
        way.setCar(bigTruck);
        way.run();
        way.setCar(smallCar);
        way.run();

        AbstractRoad street = new Street();
        street.setCar(bigTruck);
        street.run();
        street.setCar(smallCar);
        street.run();
    }
}

可以看到,通过对象组合的方式,Bridge 模式把两个角色之间的继承关系改为了聚合的关系,从而使这两者可以从容自若的各自独立的变化,这也是Bridge模式的本意。

多维度变化

结合上面的例子,增加一个维度”人”,不同的人开着不同的汽车在不同的路上行驶(三个维度);结合上面增加一个类”人”,并重新调用.

《一起学设计模式 - 桥接模式》

abstract class People {

    protected AbstractRoad abstractRoad;

    public void setAbstractRoad(AbstractRoad abstractRoad) {
        this.abstractRoad = abstractRoad;
    }

    public abstract void run();
}

class Man extends People {

    @Override
    public void run() {
        System.out.println("男人开着");
        abstractRoad.run();
    }
}

public class Client {

    public static void main(String[] args) {
        Car bigTruck = new BigTruck();
        People people = new Man();
        AbstractRoad street = new Street();
        street.setCar(bigTruck);
        people.setAbstractRoad(street);
        people.run();
    }
}

JDBC与桥接

《一起学设计模式 - 桥接模式》

通常使用JDBC连接数据库时,首选就是加载数据库驱动,然后获取数据库连接

Class.forName("数据库类驱动器");
Connection conn = DriverManager.getConnection("数据库url", "用户名", "密码");
//.................

针对不同的数据库,JDBC都可以通过java.sql.DriverManager类的静态方法getConnection(数据库url, 用户名, 密码)来获取数据库的连接。JDBC通过DriverManager对外提供了操作数据库的统一接口getConnection,通过该方法可以获取不同数据库的连接,并且通过Connection类提供的接口来进行数据的查询操作。

JDBC为不同的数据库操作提供了相同的接口,但是JDBC本身并没有针对每种数据库提供一套具体实现代码,而是通过接口java.sql.Driver的connect方法连接到了不同的数据库实现。

public interface Driver {

    Connection connect(String url, java.util.Properties info) throws SQLException;
    //其他方法
}

在JDBC中,针对不同数据库提供的统一的操作接口通过java.sql.Driver(桥)连接到了不同的数据库实现。如连接mysql数据库。

package com.mysql.jdbc;

public class NonRegisteringDriver implements java.sql.Driver { 

    public java.sql.Connection connect(String url, Properties info) throws SQLException {
        //省略具体实现代码...
    }

    //其他方法
}

具体实现

1.在com.mysql.jdbc.Driver的源码中可以看到,类加载时会将Driver注册到DriverManager中。

package com.mysql.jdbc;

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

    public Driver() throws SQLException {
        // Required for Class.forName().newInstance()
    }
}

2.在DriverManager中,调用getConnection会迭代驱动注册表中的驱动,然后调用Driver接口提供的connect方法来获取Connection对象

public class DriverManager {
    
    // JDBC驱动程序注册表
    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
    
    
    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);
    
    }
    
    private static Connection getConnection(String url, java.util.Properties info, Class<?> caller) throws SQLException {
            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 + "\")");

            SQLException reason = null;
            //遍历registeredDrivers表
            for(DriverInfo aDriver : registeredDrivers) {
                // 如果没有加载驱动的权限则跳过
                if(isDriverAllowed(aDriver.driver, callerCL)) {
                    try {
                        //调用Driver接口提供的connect方法来获取Connection对象
                        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");
        }
    }
}

上述代码中为桥接模式在JDBC中运用方式

总结

实现要点

  • Bridge模式使用对象间的组合关系解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化。

  • 所谓抽象和实现沿着各自维度的变化,即子类化它们,得到各个子类之后,便可以任意它们,从而获得不同路上的不同汽车。

  • Bridge模式有时候类似于多继承方案,但是多继承方案往往违背了类的单一职责原则(即一个类只有一个变化的原因),复用性比较差。Bridge模式是比多继承方案更好的解决方法。

  • Bridge模式的应用一般在两个非常强的变化维度,有时候,即使两个维度存在变化,但是变化的地方影响并不大,换而言之两个变化不会导致纵横交错的结果,并不一定要使用Bridge模式。

适用场景

  1. 如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的联系。

  2. 设计要求实现化角色的任何改变不应当影响客户端,或者说实现化角色的改变对客户端是完全透明的。

  3. 一个构件有多于一个的抽象化角色和实现化角色,系统需要它们之间进行动态耦合。

  4. 虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立管理这两者。

相关模式

装饰模式桥接模式在一定程度上都是为了减少子类的数目,避免出现复杂的继承关系。但是它们解决的方法却各有不同,装饰模式把子类中比基类中多出来的部分放到单独的类里面,以适应新功能增加的需要,当我们把描述新功能的类封装到基类的对象里面时,就得到了所需要的子类对象,这些描述新功能的类通过组合可以实现很多的功能组合.

– 说点什么

参考文献:http://blog.csdn.net/zlts000/article/details/26749723
参考文献:https://www.cnblogs.com/houleixx/archive/2008/02/23/1078877.html
参考文件:http://www.cnblogs.com/-crazysnail/p/3977815.html

全文代码:https://gitee.com/battcn/design-pattern/tree/master/Chapter6/battcn-brideg

  • 个人QQ:1837307557

  • battcn开源群(适合新手):391619659

微信公众号:battcn(欢迎调戏)

    原文作者:设计模式
    原文地址: https://segmentfault.com/a/1190000011931181
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞