当一个抽象可能有多个实现时,通常用继承来协调它们。抽象类定义对该抽象的接口,而具体的子类则有不同的方式加以实现。但是此方法有时候不够灵活。继承机制将抽象部分与它的实现部分固定在一起,使得难以对抽象部分和实现部分独立地进行修改、扩充和重用。先来看一个示例,画不同颜色的图形的示例,如下采用继承的方式来做:
有什么问题?
1、如果新增加一种图形如,三角形Rectangle,需要做什么?
2、如果再增加一种颜色,还需要做什么?
从上图的继承方式我们可以发现,如果增加一个新的图形,如Rectangle,我们需要增加RedRectangle和BlueRectangle两个实现类;如果再次基础上,再增加一种颜色,如黄色,那么需要再增加3个子类,包括括YellowCircle、YellowSquare和YellowRectangle。试想一下,如果后面还要增加新的图形、增加新的颜色将会导致类爆炸的情况,所有的子类的个数将等于图形的数量(A)乘 颜色的数量(B),即A*B个。这是不可接受的,本文将介绍桥接模式,一起来看看桥接模式下会有什么变化。
一. 桥接模式的基本介绍
意图
将抽象部分与它的实现部分分离,使它们都可以独立的变化。
结构
桥接模式的基本结构如下:
这里涉及到的参与者有如下几种:
- Abstract(抽象化)角色
- 抽象化给出的定义,并保存一个对实现化对象的引用。
- RefinedAbstarct(修正抽象化)角色
- 扩展抽象化角色,改变和修正父类对抽象化的定义。
- Implementor(实现化)角色
- 这个角色给出实现化角色的接口,但不给出具体的实现。必须指出的是,这个接口不一定和抽象化角色的接口定义相同,实际上,这两个接口可以非常不一样。实现化角色应用只给出底层操作,而抽象化角色应当只给出基于底层操作的更高一层的操作。
- ConcreteImplementor(具体实现化)角色
- 这个角色给出实现化角色接口的具体实现。
二. 桥接模式的示例
接下来,我们就使用上述示例,使用桥接模式来完成不同图形的绘制,使用不同颜色填充的功能。
- Color.java(Implementor角色)
package com.wangmengjun.tutorial.designpattern.bridge;
public interface Color {
void fill();
}
- Red.java和Blue.java(ConcreteImplementor具体实现化角色 )
package com.wangmengjun.tutorial.designpattern.bridge;
public class Red implements Color {
@Override
public void fill() {
System.out.println("使用红色填充");
}
}
代码语言:javascript复制package com.wangmengjun.tutorial.designpattern.bridge;
public class Blue implements Color{
@Override
public void fill() {
System.out.println("使用蓝色填充");
}
}
- Shape.java(抽象化角色)
package com.wangmengjun.tutorial.designpattern.bridge;
public abstract class Shape {
protected Color color;
public Shape(Color color) {
this.color = color;
}
public abstract void draw();
}
- Cirlce.java和Rectangle.java
package com.wangmengjun.tutorial.designpattern.bridge;
public class Circle extends Shape {
public Circle(Color color) {
super(color);
}
@Override
public void draw() {
System.out.print("绘制圆形,");
color.fill();
}
}
代码语言:javascript复制package com.wangmengjun.tutorial.designpattern.bridge;
public class Square extends Shape {
public Square(Color color) {
super(color);
}
@Override
public void draw() {
System.out.print("绘制正方形,");
color.fill();
}
}
- Client.java(客户端)
package com.wangmengjun.tutorial.designpattern.bridge;
public class Client {
public static void main(String[] args) {
/**
* 1、绘制正方形
*/
System.out.println("绘制正方形- Shape circle= new Square(new Blue())");
Shape square = new Square(new Blue());
square.draw();
square = new Square(new Red());
square.draw();
/**
* 2、绘制圆形
*/
System.out.println("绘制圆形- Shape circle= new Circle(new Blue())");
Shape circle= new Circle(new Blue());
circle.draw();
circle= new Circle(new Red());
circle.draw();
}
}
输出结果:
代码语言:javascript复制绘制正方形- Shape circle= new Square(new Blue())
绘制正方形,使用蓝色填充
绘制正方形,使用红色填充
绘制圆形- Shape circle= new Circle(new Blue())
绘制圆形,使用蓝色填充
绘制圆形,使用红色填充
至此,一个图形采用不同颜色绘制的桥接模式示例就完成了。
再来看下本文前面提出的两个问题:
问题?
1、如果新增加一种图形如,三角形Rectangle,需要做什么?
2、如果再增加一种颜色,还需要做什么?
- 问题1:增加一个三角形图形Rentangle
package com.wangmengjun.tutorial.designpattern.bridge;
public class Rectangle extends Shape {
public Rectangle(Color color) {
super(color);
}
@Override
public void draw() {
System.out.print("绘制三角形,");
color.fill();
}
}
绘制不同颜色的三角形:
代码语言:javascript复制 /**
* 3、绘制圆形
*/
System.out.println("绘制三角形- Shape = new Rectangle(new Blue())");
Shape rectangle= new Rectangle(new Blue());
rectangle.draw();
rectangle= new Circle(new Red());
rectangle.draw();
输出
代码语言:javascript复制绘制三角形- Shape = new Rectangle(new Blue())
绘制三角形,使用蓝色填充
绘制圆形,使用红色填充
我们可以看到,增加一个三角形图形类之后,其只需要增加一个子类即可,与实现化接口Color是解耦的,即不影响Color。
同样,我们再来看下第二个问题:
- 问题2:增加一个新的颜色Yellow
package com.wangmengjun.tutorial.designpattern.bridge;
public class Yellow implements Color{
@Override
public void fill() {
System.out.println("使用黄色填充");
}
}
来看下原来的图形(Circle和Square)如何使用新增加Yellow颜色来绘制图形:
代码语言:javascript复制 /**
* 1、绘制正方形
*/
System.out.println("绘制正方形,使用黄色填充- Shape square= new Square(new Yellow())");
Shape square = new Square(new Yellow());
square.draw();
/**
* 2、绘制圆形
*/
System.out.println("绘制圆形,使用黄色填充- Shape circle= new Circle(new Yellow())");
Shape circle= new Circle(new Yellow());
circle.draw();
/**
* 3、绘制三角形
*/
System.out.println("绘制三角形, 使用黄色填充- Shape rectangle = new Rectangle(new Yellow())");
Shape rectangle= new Rectangle(new Blue());
rectangle.draw();
输出:
代码语言:javascript复制绘制正方形,使用黄色填充- Shape square= new Square(new Yellow())
绘制正方形,使用黄色填充
绘制圆形,使用黄色填充- Shape circle= new Circle(new Yellow())
绘制圆形,使用黄色填充
绘制三角形, 使用黄色填充- Shape rectangle = new Rectangle(new Yellow())
绘制三角形,使用蓝色填充
可以看出,新增加一个具体的实现化类,对抽象的部分将不影响。
结论
无论增加新的具体化角色还是具体的抽象化,其只要增加一个子类即可。抽象和实现是解耦的,非常有利于扩展和维护。
三. 小结
3.1 优缺点
优点:
1、分离接口及其实现部分。一个实现未必不变地绑定在一个接口上。抽象类的实现可以在运行时刻进行配置,一个对象甚至可以在运行时刻改变它的实现。将Abstraction和Implementor分离有助于降对实现部分编译时刻的依赖性,当改变一实现类时,并不需要重新编译Abstract类和它的客户程序。为了保证一个类库的不变版本之间的二进制兼容性,一定要有这个性质。
2、提高可扩充性。你可以独立地对Abstraction和Implementor层次结构进行扩充。
3、实现细节对客户透明。你可以对客户隐藏实现细节,例如共享Implementor对象以及相应的应用计数机制(如果有的话)。
缺点:
1、桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
3.2 桥接模式之JDBC驱动
由于JDBC驱动器的存在,应用程序可以不依赖于数据库引擎的细节而独立地演化。同时数据库引擎也可以独立于应用系统的细节而独立演化。两个独立的等级结构如下图所示,左边是JDBC API的等级结构,右边是JDBC驱动的等级结构。应用程序是建立在JDBC API的基础之上的。应用系统通过委派与JDBC驱动相互作用,这是一个桥梁模式的例子。
3.3 适合场景
类的抽象以及它的实现都应该可以通过子类的方法加以扩充。这时桥接模式使你可以对不同的抽象接口以及它的实现部分进行组合并分别对它们进行扩充。针对两个独立维度的变化,我们可以通过画坐标的方式来列出桥接模式的抽象和实现化。如,本文的示例,抽象化为图形(包括圆形、正方形、三角形),实现化为颜色填充(包括红色、蓝色、黄色 ... ... )。又如,支付和支付实现,其抽象化可以是支付渠道(如支付宝、微信以及其他类型),其支付方式可以采用密码、指纹、刷脸。
其实,我们思考一下还能想到很多适合或者应用的桥接模式的场景,比如图表展示,我们可以采用ECharts、Three.js等来完成。又如系统间的通信,我们可以采用Restful API, RPC(grpc 、thrift等)以及消息队列MQ来做等等。
先写到这里吧
参考
[1]. 阎宏. Java与模式.电子工业出版社
[2]. Erich Gamma. 设计模式-可复用面向对象软件的基础. 机械工业出版社.
[3]. https://refactoring.guru/design-patterns/bridge