设计模式学习笔记(十):桥接模式
1 概述
1.1 引言
桥接模式是一种很实用的结构型设计模式,如果软件系统中某个类存在两个独立变化的维度,通过该模式可以将这两个维度分离出来,使两者可以独立扩展,让系统变得更加符合SRP。
比如,设计一个跨平台的图像浏览系统,支持的图片格式包括:
PNG
BMP
JPG
GIF
等等,而支持的系统包括:
Windows
Unix
Linux
等等,这样,系统与图片格式就是两个不同的维度,可以利用桥接模式将这两个维度分离,使得它们可以独立变化,增加新的图片格式或者新的系统时,都不会对另一个维度造成任何影响。
1.2 定义
桥接模式:将抽象部分与其实现部分分离,使它们都可以独立地变化。
它是一种对象结构型模式,又称为柄体模式或者接口模式。
1.3 结构图
1.4 角色
Abstraction
(抽象类):用于定义抽象类的接口,一般是抽象类而不是接口,具有一个Implementor
的成员,与Implementor
为关联关系,既可以包含抽象的业务方法,也可以包含具体业务方法RefinedAbstraction
(扩充抽象类):扩充由Abstraction
定义的接口,通常为具体类,实现了在Abstraction
中的抽象业务方法,同时可以调用Implementor
中的业务方法Implementor
(实现类接口):实现类的接口,相比起Abstractoin
提供的更多更复杂的操作,Implementor
一般只提供基本操作,具体实现交由子类处理ConcreteImplementor
(具体实现类):具体实现Implementor
接口,不同的ConcreteImplementor
提供不同实现的基本操作
2 典型实现
2.1 步骤
- 识别维度:首先识别出系统中两个独立变化的维度,设计出抽象类以及实现类接口
- 建立抽象耦合:在抽象层建立一个抽象关联,也就是在抽象类以及实现类接口之间建立抽象关联
- 继承:识别出维度后,将它们设计为两个独立的继承等级结构,设计出扩充抽象类以及具体类实现,比如上面的图片格式以及系统,将图片格式与系统设为抽象层,而
BMP
,GIF
等继承图片格式抽象层,Windows
等具体系统集成系统抽象层 - 扩展:建立抽象耦合后,根据需要对两个维度进行独立扩展,比如增加新的图片格式
WBEP
,增加新的操作系统Mac OS
等
2.2 抽象类与实现类接口
首先对系统中独立变化的维度进行识别,比如有两个维度(A与B),其中A设计为抽象类,B设计为接口:
abstract class DimensionA{}
interface DimensionB{}
接着是建立抽象耦合,A维度包含一个B维度成员,将B维度作为setter参数传入A维度,同时定义两个普通方法:
abstract class DimensionA
{
protected DimensionB dimensionB;
public abstract void methodA();
public void setDimensionB(DimensionB dimensionB)
{
this.dimensionB = dimensionB;
}
}
interface DimensionB
{
void methodB(String str);
}
2.3 扩充抽象类以及具体类实现
接着是扩充抽象类以及具体类实现,维度A有三个具体类,维度B有两个具体类:
class A1 extends DimensionA
{
@Override
public void methodA()
{
dimensionB.methodB("A1");
}
}
class A2 extends DimensionA
{
@Override
public void methodA()
{
dimensionB.methodB("A2");
}
}
class A3 extends DimensionA
{
@Override
public void methodA()
{
dimensionB.methodB("A3");
}
}
class B1 implements DimensionB
{
@Override
public void methodB(String str)
{
System.out.println("B1---"+str);
}
}
class B2 implements DimensionB
{
@Override
public void methodB(String str)
{
System.out.println("B2---"+str);
}
}
2.4 客户端
针对抽象层(两个维度)进行编程,将B维度作为setter参数传入A维度,接着调用A维度的方法:
public static void main(String[] args)
{
DimensionA dimensionA = new A1();
dimensionA.setDimensionB(new B1());
dimensionA.methodA();
}
3 实例
跨平台的图片浏览系统,支持的图片格式包括PNG,JPG,BMP,GIF等,支持的系统包括Linux,Unix,Windows等,使用桥接模式设计。
设计如下:
- 两个维度:图片维度+系统维度
- 抽象类:
Image
,Image
具有一个ImageShow
的成员变量 - 扩充抽象类:
BMP
,GIF
等继承Image
- 实现类接口:
ImageShow
- 具体实现类:
Linux
,Unix
,Windows
实现图片显示接口ImageShow
代码如下:
public class Test
{
public static void main(String[] args) {
Image image = new GIF();
image.setImageShow(new Linux());
image.show();
}
}
//Image抽象类
abstract class Image
{
protected ImageShow imageShow;
public void setImageShow(ImageShow imageShow)
{
this.imageShow = imageShow;
}
public abstract show();
}
class BMP extends Image
{
@Override
public void show()
{
imageShow.show("BMP");
}
}
class GIF extends Image
{
@Override
public void show()
{
imageShow.show("GIF");
}
}
class PNG extends Image
{
@Override
public void show()
{
imageShow.show("PNG");
}
}
class JPG extends Image
{
@Override
public void show()
{
imageShow.show("JPG");
}
}
//图片显示接口
interface ImageShow
{
void show(String name);
}
class Windows implements ImageShow
{
@Override
public void show(String name)
{
System.out.println("Windows show "+name);
}
}
class Linux implements ImageShow
{
@Override
public void show(String name)
{
System.out.println("Linux show "+name);
}
}
class Unix implements ImageShow
{
@Override
public void show(String name)
{
System.out.println("Unix show "+name);
}
}
更换图片格式只需要修改Image
的父类:
Image image = new GIF();
Image image = new BMP();
Image image = new JPG();
Image image = new PNG();
而更换操作系统只需要修改传入setter的参数:
image.setImageShow(new Linux());
image.setImageShow(new Windows());
image.setImageShow(new Unix());
这样就可以把图片以及系统两个维度分离,并能够独立扩展,增加新的图片格式,只需要增加一个新的继承Image
的类即可,增加新的系统只需实现ImageShow
接口即可。
由于例子简单使用反射进行简化代码并增加了新的系统以及图片格式,代码如下:
public class Test
{
public static void main(String[] args) {
Image image = new WBEP();
image.setImageShow(new Mac());
image.show();
}
}
abstract class Image
{
protected ImageShow imageShow;
public void setImageShow(ImageShow imageShow)
{
this.imageShow = imageShow;
}
public void show()
{
imageShow.show(getClass().getName());
}
}
class BMP extends Image{}
class GIF extends Image{}
class PNG extends Image{}
class JPG extends Image{}
class WBEP extends Image{}
interface ImageShow
{
void show(String name);
}
abstract class ImageSystem implements ImageShow
{
public void show(String name)
{
System.out.println(getClass().getName()+" show "+name);
}
}
class Windows extends ImageSystem{}
class Linux extends ImageSystem{}
class Unix extends ImageSystem{}
class Mac extends ImageSystem{}
4 主要优点
- 低耦合:分离抽象接口及其实现部分,桥接模式使用“对象间的关联关系”解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以按照各自的维度变化。也就是说,抽象和实现不再同一个继承层次中,而是让抽象作为父类,实现作为子类,这样就可以任意组合子类,从而获得多维度的组合对象
- 取代多重继承:很多情况下桥接模式可以取代多重继承,多重继承违反了SRP(单一权责原则),复用性差,而且类的个数多,桥接模式可以有效减少子类个数
- 提高扩展性:桥接模式提高了系统的扩展性,在两个维度中任意扩展一个维度,都不需要修改原有系统,符合开闭原则
5 主要缺点
- 增加理解难度:桥接模式会增加系统的理解以及设计难度,由于关联关系建立在抽象层,要求开发者一开始就针对抽象层进行设计以及编程
- 需要正确识别抽象层:桥接模式要求正确识别系统中两个独立变化的维度,因此适用范围有一定局限,正确识别独立维度需要一定经验积累
6 适用场景
- 如果一个系统需要在抽象类和具体类之间增加更多的灵活性,避免在两个层次之间建立静态的继承关系,通过桥接模式可以使它们在抽象层建立一个关联关系
- 抽象部分和实现部分可以以继承的方式独立扩展而互不影响,在程序运行时可以动态将一个抽象类子类的对象和一个实现类子类的对象进行动态组合
- 一个类存在两个(或多个)独立变化的维度,且这两个(或多个)维度都需要独立进行扩展
- 对于不希望使用继承或因为多重继承导致系统类的个数急剧增加的系统