【从零开始学设计模式】第八章_桥接模式

第八章_桥接模式

顺口溜:适装桥组享代外

1.介绍

1.1定义

桥接模式:(Bridge Pattern):将抽象部分与它的实现部分分离,使它们都可以独立地变化

意图:将抽象部分与实现部分分离,使它们都可以独立的变化。
主要解决:在有多种可能会变化的情况下,用继承会造成类爆炸问题,扩展起来不灵活。
何时使用:实现系统可能有多个角度分类,每一种角度都可能变化。
如何解决:把这种多角度分类分离出来,让它们独立变化,减少它们之间耦合。
关键代码:抽象类依赖实现类。

1.2使用场景

1.2.1生活场景

设计一种遥控车玩具,这种遥控车可以==在不同的地形上行驶,比如在公路上、草地上和沙滩上。同时,这些遥控车还可以有不同的轮胎类型==,比如普通轮胎、越野轮胎和雪地轮胎。

1.2.2java场景

  • JDBC驱动程序;

  • AWT中的Peer架构;

  • 银行日志管理:

    • 格式分类:操作日志、交易日志、异常日志
    • 距离分类:本地记录日志、异地记录日志
  • 人力资源系统中的奖金计算模块:

    • 奖金分类:个人奖金、团体奖金、激励奖金。
    • 部门分类:人事部门、销售部门、研发部门。
  • OA系统中的消息处理:

    • 业务类型:普通消息、加急消息、特急消息
    • 发送消息方式:系统内消息、手机短信、邮件

1.3角色

  • 抽象类(Abstraction):定义抽象接口,通常包含对实现接口的引用。
  • 抽象具体类(Refined Abstraction):对抽象的扩展,可以是抽象类的子类或具体实现类。
  • 实现(Implementor):定义实现接口,提供基本操作的接口。
  • 具体实现(Concrete Implementor):实现实现接口的具体类。
  • 桥接类:聚合具体实现类和实现接口的具体类

2.举例

2.1生活举例

在设计一种遥控车玩具,这种遥控车可以在不同的地形上行驶,比如在公路上、草地上和沙滩上。

同时,这些遥控车还可以有不同的轮胎类型,比如普通轮胎、越野轮胎和雪地轮胎。

在这个例子中,遥控车的行驶地形可以看作是抽象部分,而==轮胎类型可以看作是实现部分==。采用桥接模式的设计,你可以将遥控车的行驶地形和轮胎类型进行解耦,使它们可以独立地变化和扩展。

行驶地形抽象类 TerrainType:定义了遥控车行驶地形的抽象方法

// 抽象类 TerrainType:行驶地形
abstract class TerrainType {
   
    //行驶
    abstract void setTerrain();
}

// 具体类 OnRoadTerrain
class OnRoadTerrain extends TerrainType {
   
    @Override
    void setTerrain() {
   
        System.out.println("在公路上行驶");
    }
}

// 具体类 OffRoadTerrain
class OffRoadTerrain extends TerrainType {
   
    @Override
    void setTerrain() {
   
        System.out.println("在草地上行驶");
    }
}

// 具体类 BeachTerrain
class BeachTerrain extends TerrainType {
   
    @Override
    void setTerrain() {
   
        System.out.println("在沙滩上行驶");
    }
}

轮胎类型接口TireType:定义了遥控车轮胎类型的抽象方法

// 接口 TireType:轮胎类型
interface TireType {
   
    void setTire();
}

// 具体类 StandardTire
class StandardTire implements TireType {
   
    @Override
    public void setTire() {
   
        System.out.println("使用普通轮胎");
    }
}

// 具体类 OffRoadTire
class OffRoadTire implements TireType {
   
    @Override
    public void setTire() {
   
        System.out.println("使用越野轮胎");
    }
}

// 具体类 SnowTire
class SnowTire implements TireType {
   
    @Override
    public void setTire() {
   
        System.out.println("使用雪地轮胎");
    }
}

桥接类RemoteControlCar 将具体的行驶地形类和轮胎类型类进行桥接,通过组合关系将它们连接起来。这样,你就可以根据不同的需求组合不同的行驶地形和轮胎类型,灵活地创建出适应不同场景的遥控车玩具。

// 桥接类 RemoteControlCar
class RemoteControlCar {
   
    private TerrainType terrain;	//行驶地形抽象类
    private TireType tire;			//轮胎类型接口

    public RemoteControlCar(TerrainType terrain, TireType tire) {
   
        this.terrain = terrain;
        this.tire = tire;
    }

    public void drive() {
   
        this.terrain.setTerrain();	
    }

    public void changeTire() {
   
        this.tire.setTire();
    }
}

// 测试桥接模式

public class BridgePatternExample {
   
    public static void main(String[] args) {
   
        TerrainType onRoad = new OnRoadTerrain();
        TerrainType offRoad = new OffRoadTerrain();
        TerrainType beach = new BeachTerrain();

        TireType standard = new StandardTire();
        TireType offRoadTire = new OffRoadTire();
        TireType snowTire = new SnowTire();

        RemoteControlCar car1 = new RemoteControlCar(onRoad, standard);
        RemoteControlCar car2 = new RemoteControlCar(offRoad, offRoadTire);
        RemoteControlCar car3 = new RemoteControlCar(beach, snowTire);

        car1.drive();
        car1.changeTire();

        car2.drive();
        car2.changeTire();

        car3.drive();
        car3.changeTire();
    }
}

2.2JDK源码举例

image-20240213203603440

MySQL 的 Connection 接口实现的是 java.sql.Connection 接口

同时 Oracle 数据库也一样可以实现 java.sql.Connection 接口,他们向下都可以有更多的实现子类。

然后 DriverManager 相当于桥接模块,聚合 java.sql.Connection 接口和Driver接口,供客户端调用。

2.2.1总览

public class JDBCTest {
   
    public static void main(String[] args) {
   
        Connection conn = null;
        Statement stmt = null;
        try{
   
            //STEP 1: Register JDBC driver	//注册mysql的Driver,即com.mysql.jdbc.Driver类就会被加载,注册到registeredDrivers这个特殊的list中
            Class.forName("com.mysql.jdbc.Driver");
            //STEP 2: Open a connection		//获取mysql规范的连接,从registeredDrivers中来获取已经注册的driver
            conn = DriverManager.getConnection("jdbc:mysql://192.168.108.145/test", "root", "root");
            //STEP 3: Execute a query
            stmt = conn.createStatement();
            ResultSet rs = stmt.executeQuery("select * from userinfo limit 1");
            //STEP 4: Get results
            while(rs.next()){
   
                System.out.println(rs.getString("id"));
            }
            rs.close();
        }catch(SQLException se){
   
            ......
        }//end try
    }
}

2.2.2Driver

mysql的Driver

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
    public Driver() throws SQLException {
   
        // Required for Class.forName().newInstance()
    }
}

2.2.3DriverManager

DriverManager相当于桥接类,提供Connection,从registeredDrivers中来获取已经注册的driver,聚合起来获取Connection

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");
    }
 
}

3.优缺点

优点

(1)实现了抽象和实现部分的分离
桥接模式分离了抽象部分和实现部分,从而极大的提供了系统的灵活性,让抽象部分和实现部分独立开来,分别定义接口,这有助于系统进行分层设计,从而产生更好的结构化系统。对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了。

(2)可动态的切换实现
由于桥接模式实现了抽象和实现的分离,所以在实现桥接模式时,就可以实现动态的选择和使用具体的实现。

(3)更好的可扩展性
由于桥接模式把抽象部分和实现部分分离了,从而分别定义接口,这就使得抽象部分和实现部分可以分别独立扩展,而不会相互影响,大大的提供了系统的可扩展性。

(4)实现细节对客户端透明,可以对用户隐藏实现细节。

缺点

(1)桥接模式的引入增加了系统的理解和设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计和编程。

(2)桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围有一定的局限性。

4.桥接模式与适配器模式

4.1桥接模式和适配器模式的区别和联系

适配器模式和桥接模式都是 间接引用对象,因此可以使系统更灵活,在实现上都涉及从自身以外的一个接口向被引用的对象发出请求。

两种模式的区别在于使用场合的不同,适配器模式主要解决两个已经有接口间的匹配问题,这种情况下被适配的接口的实现往往是一个黑匣子。我们不想,也不能修改这个接口及其实现。同时也不可能控制其演化,只要相关的对象能与系统定义的接口协同工作即可。适配器模式经常被用在与第三方产品的功能集成上,采用该模式适应新类型的增加的方式是开发针对这个类型的适配器,如下图所示:

image-20240213211044773

桥接模式则不同,参与桥接的接口是稳定的,用户可以扩展和修改桥接中的类,但是不能改变接口。桥接模式通过接口继承实现或者类继承实现功能扩展。如下图所示:

image-20240213211127224

按照GOF的说法,桥接模式和适配器模式用于设计的不同阶段,桥接模式用于设计的前期,即在设计类时将类规划为逻辑和实现两个大类,是他们可以分别精心演化;而适配器模式用于设计完成之后,当发现设计完成的类无法协同工作时,可以采用适配器模式。然而很多情况下在设计初期就要考虑适配器模式的使用,如涉及到大量第三方应用接口的情况。

4.2适配器与桥接模式的联合

在实际应用中,桥接模式经常和适配器模式同时出现,如下图所示:

image-20240213211357290

这种情况经常出现在需要其他系统提供实现方法时,一个典型的例子是工业控制中的数据采集。不同工控厂家提供的底层数据采集接口通常不同,因此在做上层软件设计无法预知可能遇到何种接口。为此需要定义一个通用的采集接口,然后针对具体的数据采集系统开发相应的适配器。数据存储需要调用数据采集接口获得的数据,而数据可以保存到关系数据库、实时数据库或者文件中,。数据存储接口和数据采集结构成了桥接,如下图所示:

image-20240213211521225

同样的结构也经常出现在报表相关的应用中,报表本身结构和报表输出方式完全可以分开,如下图所示:

image-20240213211538847

如上图所示,报表输出可以单独抽象出来与报表的具体形式分开。但报表输出又依赖于具体的输出方式,如果需要输出为PDF格式,则需要调用与PDF相关的API,而这是设计所无法控制的,因此这里需要适配器模式。

相关推荐

  1. 开始设计模式_工厂模式

    2024-02-15 05:02:01       31 阅读
  2. 开始设计模式_适配器模式

    2024-02-15 05:02:01       31 阅读
  3. 开始设计模式】第一_设计模式简介

    2024-02-15 05:02:01       36 阅读
  4. 开始设计模式】第二_单例模式

    2024-02-15 05:02:01       28 阅读
  5. 设计模式

    2024-02-15 05:02:01       19 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-02-15 05:02:01       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-02-15 05:02:01       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-02-15 05:02:01       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-02-15 05:02:01       18 阅读

热门阅读

  1. v-model原理

    2024-02-15 05:02:01       27 阅读
  2. 数据结构-树

    2024-02-15 05:02:01       26 阅读
  3. 只用cin和cout

    2024-02-15 05:02:01       31 阅读
  4. 多态

    多态

    2024-02-15 05:02:01      26 阅读
  5. Redis缓存击穿

    2024-02-15 05:02:01       33 阅读
  6. 蓝桥杯官网填空题(质数拆分)

    2024-02-15 05:02:01       33 阅读
  7. 虚拟dom

    2024-02-15 05:02:01       31 阅读
  8. centos7 mysql8安装教程

    2024-02-15 05:02:01       27 阅读
  9. vue 封装request请求 多域名访问

    2024-02-15 05:02:01       32 阅读
  10. 限制Unity帧率的方式

    2024-02-15 05:02:01       36 阅读