桥接设计模式

Gof 原文

Decouple an abstraction from its implementation so that the two can varyindependently.

在这里插入图片描述

当一个类内部具备两种或多种变化维度时,使用桥接模式可以解耦这些变化的维度,使高层代码架构稳定。
桥接模式适用于以下几种业务场景。
(1)在抽象和具体实现之间需要增加更多灵活性的场景。
(2)一个类存在两个(或多个)独立变化的维度,而这两个(或多个)维度都需要独立进行扩展。
(3)不希望使用继承,或因为多层继承导致系统类的个数剧增。

继承拥有很多优点,比如,抽象、封装、多态等,父类封装共性,子类实现特性。继承可以很好地实现代码复用(封装)的功能,但这也是继承的一大缺点。因为父类拥有的方法,子类也会继承得到,无论子类需不需要,这说明继承具备强侵入性(父类代码侵入子类),同时会导致子类臃肿。
因此,在设计模式中,有一个原则为优先使用组合/聚合,而不是继承。

The implementation of bridge design pattern follows the notion to prefer Composition over inheritance.
桥接设计模式的实现遵循组合优于继承的概念。

在这里插入图片描述
问题的引出

类内部具备两种或多种变化维度时,比如图形的形状和颜色。

public interface Shape {
    void draw();
}
class Round implements Shape {
    @Override
    public void draw() {
        System.out.println("绘制圆形");
    }
}
public interface Color {
	public void getColor();
}
public class Red implements Color {
    @Override
    public String getColor() {
        return "红";
    }
}

如果想要红色的圆,这时我们很容易想到两种设计方案:

1、为了复用形状类,将每种形状定义为父类,每种不同颜色的图形继承自其形状父类。

class RedRound extends Round {
    @Override
    public void draw() {
        System.out.println("绘制红色的圆形");
    }
}

2、为了复用颜色类,将每种颜色定义为父类,每种不同颜色的图形继承自其颜色父类。

public class RedRound extends Red {
    @Override
    public String getColor() {
        return "绘制红色的圆形";
    }
}

一旦有5个颜色5个形状,就要写25个类!!!这时使用继承很容易造成子类越来越多。

形状和颜色,都是图形的两个属性。他们两者的关系是平等的,所以不属于继承关系。

更好的的实现方式是:将形状和颜色分离,根据需要对形状和颜色进行组合,这就是桥接模式的思想。

下面是理解过程

颜色的接口

public interface Color {
	public void applyColor();
}

颜色的实现类

public class GreenColor implements Color {
	public void applyColor(){
		System.out.println("green.");
	}
}
public class RedColor implements Color {
	public void applyColor(){
		System.out.println("red.");
	}
}

将形状和颜色分离,根据需要对形状和颜色进行组合,代码里体现就是把原来两个类(Is-a)继承关系,现在变成了组合 Has-a 的关系。

在每个形状类中桥接 Color 接口:

public abstract class Shape {
    protected IColor color;

    public Shape(IColor c) {
        this.color = c;
    }

    public abstract void applyColor();
}

这样就是 将抽象与其实现解耦,Shape 里不关心 Color 具体实现,只是使用Color 接口的方法,Color 因为是接口所以具体实现有很灵活

此时各种颜色可以和各种形状组合,就是 多对多 的关系了。

public class Pentagon extends Shape {

	public Pentagon(IColor c) {
		super(c);
	}

	@Override
	public void applyColor() {
		System.out.println("Pentagon filled with color!! ");
		color.applyColor();
	} 
}
public class Triangle extends Shape {

    public Triangle(IColor c) {
        super(c);
    }

    @Override
    public void applyColor() {
        System.out.println("Triangle filled with color!! ");
        color.applyColor();
    }
}

测试

A2_Shape tri = new A3_Triangle(new A5_RedColor());
tri.applyColor();
		
A2_Shape pent = new A4_Pentagon(new A6_GreenColor());
pent.applyColor();

Triangle filled with color!! 
red.
Pentagon filled with color!! 
green.

在这里插入图片描述
上文是读完下文修改的版本,下文才是思维源头(图解设计模式里的文章)

先介绍两个概念

希望增加新功能时

想给一个类增加新功能时,会创建这个类的子类增加新的方法。

  • 父类具有基本功能
  • 在子类中增加新的功能
  • 这种层次结构称为类的功能层次结构

希望增加新的实现时

  • 父类通过声明抽象方法来定义接口(API)
  • 子类通过实现具体方法来实现接口(API)
  • 这种层次结构称为类的实现层次结构

当类的层次结构只有一层时,功能层次结构与实现层次结构是混杂在一个层次结构中的,这样容易使类的层次结构变得复杂,因为要把他们分开,但是分开后会缺少联系,多以要在他们之间搭一座桥,这就是Bridge模式的作用

Bridge模式的作用就是连接类的功能层次结构类的实现层次结构

public class Display {
    private IPrint impl;

    public Display(IPrint impl) {
        this.impl = impl;
    }

    public void open() {
        impl.rawOpen();
    }

    public void print() {
        impl.rawPrint();
    }

    public void close() {
        impl.rawClose();
    }

    public final void display() {
        open();
        print();
        close();
    }
}

注意:private DisplayImpl impl; 其中impl就是两个层次结构的桥梁

子类增加新功能,此为类的功能层次结构的体现

public class MultiDisplay extends Display {
    public MultiDisplay(IPrint impl) {
        super(impl);
    }

    public void multiDisplay(int times) {
        open();
        for (int i = 0; i < times; i++) {
            print();
        }
        close();
    }
}

以下为类的实现层次结构

public interface IPrint {
    void rawOpen();

    void rawPrint();

    void rawClose();
}
public class StringPrintImpl implements IPrint {
    private final String string;
    private final int width;

    public StringPrintImpl(String string) {
        this.string = string;
        this.width = string.getBytes().length;
    }

    public void rawOpen() {
        printLine();
    }

    public void rawPrint() {
        System.out.println("|" + string + "|");
    }

    public void rawClose() {
        printLine();
    }

    private void printLine() {
        System.out.print("+");
        for (int i = 0; i < width; i++) {
            System.out.print("-");
        }
        System.out.println("+");
    }
}

使用

Display d1 = new Display(new StringPrintImpl("Hello, China."));
d1.display();

Display d2 = new MultiDisplay(new StringPrintImpl("Hello, World."));
d2.display();

MultiDisplay d3 = new MultiDisplay(new StringPrintImpl("Hello, Universe."));
d3.display();
d3.multiDisplay(5);

结果

+-------------+
|Hello, China.|
+-------------+
+-------------+
|Hello, World.|
+-------------+
+----------------+
|Hello, Universe.|
+----------------+
+----------------+
|Hello, Universe.|
|Hello, Universe.|
|Hello, Universe.|
|Hello, Universe.|
|Hello, Universe.|
+----------------+

在这里插入图片描述

桥接模式在JDK源码中的应用

大家非常熟悉的JDBC API,其中有一个Driver类就是桥接对象
我们都知道,在使用的时候通过Class.forName()方法可以动态加载各个数据库厂商实现的Driver类。
以MySQL的实现为例,具体客户端应用代码如下。

Class.forName("com.mysql.jdbc.Driver");
conn = (Connection) DriverManager.getConnection("jdbc:mysql://localhost:3306/ssm", "root", "123456");
ps = (PreparedStatement) conn.prepareStatement("select * from flower");
rs = ps.executeQuery();

Driver在JDBC中并没有做任何实现,具体的功能实现由各厂商完成。
我们以MySQL的实现为例

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

当我们执行Class.forName("com.mysql.jdbc.Driver")方法的时候,就会对类进行加载,就会执行com.mysql.jdbc.Driver类的 静态块 中的代码。而静态块中的代码只是调用了一下DriverManager的registerDriver()方法,然后将Driver对象注册到DriverManager中。
继续跟进DriverManager类,来看相关的代码。

public class DriverManager {

...

    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();

...

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

...

...

    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 {
        if(driver != null) {
            registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
        } else {
            throw new NullPointerException();
        }
        println("registerDriver: " + driver);
    }

...

接下来继续执行客户端代码的第二步,调用DriverManager的getConnection()方法获取连接对象

public class DriverManager {

...

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

...

private static Connection getConnection( String url, java.util.Properties info, Class<?> caller) throws SQLException {
        ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
        synchronized(DriverManager.class) {
            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;
        for(DriverInfo aDriver : registeredDrivers) {
            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 (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");
    }

...

getConnection()中,又会调用各自厂商实现的Driver的connect()方法获得连接对象。这样就巧妙地避开了使用继承,为不同的数据库提供了相同的接口。

JDBC API中的DriverManager就是桥,如下图所示。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值