资讯专栏INFORMATION COLUMN

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

tinysun1234 / 1200人阅读

摘要:桥接模式属于结构型模式的一种,用于把抽象化与实现化解耦,使得二者可以独立变化,它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。相关模式装饰模式与桥接模式在一定程度上都是为了减少子类的数目,避免出现复杂的继承关系。

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

概述

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

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

合成复用原则

合成复用原则又称为组合/聚合复用原则(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 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模式。

适用场景

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

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

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

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

相关模式

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

- 说点什么

参考文献: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://www.ucloud.cn/yun/67998.html

相关文章

  • 设计模式在jdk中的应用

    摘要:本文只是寻找设计模式在中的应用。来补全这一块工厂模式中的应用包线程池解释和代码线程池中有线程创建工厂。状态模式中的应用解释和代码根据一个指针的状态而改变自己的行为适配器模式中的应用解释和代码将一个类的接口转换成客户希望的另外一个接口。 前言 最近重学设计模式,而且还有很多源码要看。所以就想一举两得。从源码中寻找设计模式。顺便还可以看看源码。。。本文只是寻找设计模式在java中的应用。优...

    dingding199389 评论0 收藏0
  • 编程中的那些套路——关于桥接模式

    摘要:该文章属于编程中的那些经典套路设计模式汇总系列,并且以下内容基于语言今天来谈谈桥接模式,桥接模式的功能在于将两个原本不相关的类结合在一起,然后利用两个类中的方法和属性,输出一份新的结果。 该文章属于《编程中的那些经典套路——设计模式汇总》系列,并且以下内容基于语言PHP 今天来谈谈桥接模式,桥接模式的功能在于将两个原本不相关的类结合在一起,然后利用两个类中的方法和属性,输出一份新的结果...

    xioqua 评论0 收藏0
  • java篇 - 收藏集 - 掘金

    摘要:进阶多线程开发关键技术后端掘金原创文章,转载请务必将下面这段话置于文章开头处保留超链接。关于中间件入门教程后端掘金前言中间件 Java 开发人员最常犯的 10 个错误 - 后端 - 掘金一 、把数组转成ArrayList 为了将数组转换为ArrayList,开发者经常... Java 9 中的 9 个新特性 - 后端 - 掘金Java 8 发布三年多之后,即将快到2017年7月下一个版...

    OpenDigg 评论0 收藏0
  • JS 桥接模式

    摘要:简介桥接模式将抽象部分与它的实现部分分离,使它们都可以独立地变化。同时桥接模式也有自己的缺点大量的类将导致开发成本的增加,同时在性能方面可能也会有所减少。 1. 简介 桥接模式(Bridge)将抽象部分与它的实现部分分离,使它们都可以独立地变化。其实就是函数的封装,比如要对某个DOM元素添加color和backgroundColor,可以封装个changeColor函数,这样可以在多个...

    chemzqm 评论0 收藏0

发表评论

0条评论

tinysun1234

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<