5、桥接模式
桥接模式
问题引入
- 在现实生活中,某些类具有两个或多个维度的变化,如图形既可按形状分,又可按颜色分。如何设计类似于 Photoshop 这样的软件,能画不同形状和不同颜色的图形呢?如果用继承方式,m 种形状和 n 种颜色的图形就有 m×n 种,不但对应的子类很多,而且扩展困难。
- 当然,这样的例子还有很多,如不同颜色和字体的文字、不同品牌和功率的汽车、不同性别和职业的男女、支持不同平台和不同文件格式的媒体播放器等。如果用桥接模式就能很好地解决这些问题。
现在对不同手机类型的不同品牌实现操作编程(比如:开机、关机、上网,打电话等),如下:
- 手机
- 折叠屏
- 小米
- 华为
- vivo
- 直立屏
- 小米
- 华为
- vivo
- 旋转屏
- 小米
- 华为
- vivo
- 折叠屏
手机又两个维度,屏幕类型和品牌类型,如果十一你个接口实现,m中屏幕类型,n中品牌,将会又m*n中组合,类的数量爆炸式增长;
传统方式解决手机问题类图
传统方式解决手机问题缺点
- 扩展性问题(类爆炸),如果我们再增加手机的样式(旋转式),就需要增加各个品牌手机的类,同样如果我们增加一个手机品牌,也要在各个手机样式类下增加。
- 违反了单一职责原则,当我们增加手机样式时,要同时增加所有品牌的手机,这样增加了代码维护成本.
- 解决方案-使用桥接模式
桥接模式
桥接(Bridge)模式的定义如下:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
基本介绍
- 桥接模式(Bridge 模式)是指:将实现与抽象放在两个不同的类层次中,使两个层次可以独立改变。
- 是一种结构型设计模式
- Bridge 模式基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责。它的主要特点是把抽象(Abstraction)与行为实现(Implementation)分离开来,从而可以保持各部分的独立性以及应对他们的功能扩展
桥接模式类图
可以将抽象化部分与实现化部分分开,取消二者的继承关系,改用组合关系。
类图
说明
桥接(Bridge)模式包含以下主要角色。
- 抽象化(Abstraction)角色:定义抽象类,维护了 Implementor / 即它的实现类 ConcreteImplementorA.., 二者是聚合关系, Abstraction充当桥接类
- 扩展抽象化(Refined Abstraction)角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
- 实现化(Implementor)角色:定义实现化角色的接口,供扩展抽象化角色调用,行为实现类的接口
- 具体实现化(Concrete Implementor)角色:给出实现化角色接口的具体实现,行为的具体实现类
下面我们使用不同形状绘制不同颜色案例来说明桥接模式;
桥接模式简单实现
抽象化角色
public abstract class Shape {
/*添加一个颜色的成员变量以调用ColorAPI 的方法来实现给不同的形状上色*/
protected ColorAPI colorAPI;
/*注入颜色成员变量*/
public void setDrawAPI(ColorAPI colorAPI) {
this.colorAPI= colorAPI;
}
public abstract void draw();
}
此例子中,形状Shape充当抽象化角色,抽象化角色内部持有Implementor 引用,抽象角色和接口是组合关系,抽象角色充当桥接;
扩展抽象化
扩展抽象化继承抽象类,实现抽象类中定义的具体动作,在本例中,抽象的形状角色可以有多个扩展的子类,比如圆,长方形,三角形等,下面实现圆和长方形两个具体角色;
//长方形实现
public class Rectangle extends Shape{
@Override
public void draw() {
System.out.print("我是长方形");
colorAPI.paint();
}
}
//圆形实现
public class Circle extends Shape{
@Override
public void draw() {
System.out.print("我是圆形");
colorAPI.paint();
}
}
实现化角色
public interface ColorAPI {
/*颜色接口,用于绘制不同的颜色*/
public void paint();
}
实现化角色是一个接口,供扩展抽象化角色调用,可以对抽象角色进行扩展;
具体实现化
具体实现化实现接口内容,在本例中即绘制不同的颜色,因此本例中实现红色和蓝色;
//蓝色实现
public class BlueColorAPI implements ColorAPI{
/*实现颜色绘制接口,绘制不同的颜色*/
@Override
public void paint() {
System.out.println("画上蓝色");
}
}
//红色实现
public class RedColorAPI implements ColorAPI{
@Override
public void paint() {
System.out.println("画上红色");
}
}
客户端
public class Bridge_test_API {
public static void main(String[] args) {
//创建一个圆形
Shape shape = new Circle();
//给圆形蓝色的颜料
shape.setDrawAPI(new BlueColorAPI());
//上色
shape.draw();
//创建一个长方形
Shape shape1 = new Rectangle();
//给长方形红色的颜料
shape1.setDrawAPI(new RedColorAPI());
//上色
shape1.draw();
}
}
客户端通过调用形状抽象类,注入不同的颜色引用就可以绘制不同颜色的形状;
分析桥接模式的实现,本质上是抽象类中注入了接口的引用,通过注入不同接口的实现,扩展不同的功能;
比如现在想增加一个绿色三角形,那三角形类直接继承Shape抽象类,绿色实现Color接口即可,然后在接口中直接使用,不需要对原有的代码进行修改;
现在再来看“将抽象部分与他的实现部分分离”这句话,实际上就是在说实现系统可能有多个角度分类(例如例子中的形状与颜色),每一种分类都有可能变化,那么把这种多角度分离出来让他们独立变化,减少他们之间的耦合。
使用桥接模式解决手机问题
使用桥接模式改进传统方式,让程序具有搞好的扩展性,利用程序维护
类图
- Phone就相当于Abstraction抽象类
- Brand是实现化接口Implementor
- foldedPhone,UpRightPhone是抽象类的子类
- Brand是接口是实现化角色的接口。
抽象手机类
//抽象层
abstract class Phone{
//使用聚合的方式
private Brand brand;
public Phone(Brand brand) {
this.brand = brand;
}
protected void open(){
this.brand.open();
}
protected void close(){
this.brand.close();
}
protected void call(){
this.brand.call();
}
}
具体手机类
class FoldedPhone extends Phone{
public FoldedPhone(Brand brand) {
super(brand);
}
public void open(){
super.open();
System.out.println("打开折叠样式的手机");
}
public void close(){
super.close();
System.out.println("关闭折叠样式的手机");
}
public void call(){
super.call();
System.out.println("用折叠样式手机打电话");
}
}
//现在增加一个直立式手机的样式,非常简单
class UpRightPhone extends Phone{
public UpRightPhone(Brand brand) {
super(brand);
}
public void open(){
super.open();
System.out.println("打开直立式样式的手机");
}
public void close(){
super.close();
System.out.println("关闭直立式式的手机");
}
public void call(){
super.call();
System.out.println("用直立式手机打电话");
}
}
品牌接口
interface Brand{
void open();
void close();
void call();
}
具体品牌
class HW implements Brand{
@Override
public void open() {
System.out.println("华为手机开机了");
}
@Override
public void close() {
System.out.println("华为手机关机了");
}
@Override
public void call() {
System.out.println("华为手机打电话");
}
}
class XM implements Brand{
@Override
public void open() {
System.out.println("小米手机开机了");
}
@Override
public void close() {
System.out.println("小米手机关机了");
}
@Override
public void call() {
System.out.println("小米手机打电话");
}
}
客户端
public class BridgeDemo {
public static void main(String[] args) {
// 获取折叠手机(样式+品牌)
Phone foldedPhone = new FoldedPhone(new HW());
foldedPhone.open();
foldedPhone.call();
foldedPhone.close();
// 使用直立式手机
Phone upRightPhone = new UpRightPhone(new XM());
upRightPhone.open();
upRightPhone.call();
upRightPhone.close();
}
}
桥接模式在JDBC源码包中的应用
- Jdbc 的 Driver 接口,如果从桥接模式来看,Driver 就是一个接口,下面可以有 MySQL 的 Driver,Oracle 的Driver,这些就可以当做实现接口类
- 代码分析+Debug 源码
对 jdbc 源码分析的类图
桥接模式注意事项
- 实现了抽象和实现部分的分离,从而极大的提供了系统的灵活性,让抽象部分和实现部分独立开来,这有助于系统进行分层设计,从而产生更好的结构化系统。
- 对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了,其它的部分由具体业务来完成。
- 桥接模式替代多层继承方案,可以减少子类的个数,降低系统的管理和维护成本。
- 桥接模式的引入增加了系统的理解和设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计和编程
- 桥接模式要求正确识别出系统中两个独立变化的维度**(抽象、和实现)**,因此其使用范围有一定的局限性,即需要有这样的应用场景。
优缺点
优点
实现抽象和实现的分离
桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统
桥接模式有时类似于多继承方案,但是多继承方案违背了类的单一职责原则(即一个类只有一个变化的原因),复用性比较差,而
且多继承结构中类的个数非常庞大,桥接模式是比多继承方案更好的解决方法
缺点
- 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
- 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性。
- 由于聚合关系建立在抽象层,要求开发者针对抽象化进行设计与编程,能正确地识别出系统中两个独立变化的维度,这增加了系统的理解与设计难度。
应用场景
当一个类内部具备两种或多种变化维度时,使用桥接模式可以解耦这些变化的维度,使高层代码架构稳定。
桥接模式通常适用于以下场景。
- 当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时。
- 当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时。
- 当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时。
桥接模式的一个常见使用场景就是替换继承。我们知道,继承拥有很多优点,比如,抽象、封装、多态等,父类封装共性,子类实现特性。继承可以很好的实现代码复用(封装)的功能,但这也是继承的一大缺点。
因为父类拥有的方法,子类也会继承得到,无论子类需不需要,这说明继承具备强侵入性(父类代码侵入子类),同时会导致子类臃肿。因此,在设计模式中,有一个原则为优先使用组合/聚合,而不是继承。