概述
桥接模式是一种很实用的结构设计模式,如果软件系统中某个类存在两个独立变化的维度,通过该模式可以将两个维度分离出来,使两者可以独立扩展,让系统更加符合单一职责原则。与多层继承方案不同,它将两个独立变化的维度设计为两个独立的继承等级结构,并且在抽象层建立一个抽象关联,该关联关系类似一条连接两个独立继承结构的桥,故名为桥接模式
桥接模式用一种巧妙的方式处理多层继承存在的问题,用抽象关联取代了传统的多层继承,将类之间的静态继承关系转换为动态的对象组合关系,使得系统更加灵活,并易于扩展,同时有效控制了系统中类的个数
定义
桥接模式(Bridge Pattern):将抽象部分与其实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式
结构图
Abstraction(抽象类):用于定义抽象类的接口,它一般是抽象类而不是接口,其中定义了一个Implementor(实现类接口)类型的对象并可以维护该对象,它与Implementor之间具有关联关系,它既可以包含抽象业务方法,也可以包含具体业务方法
RefinedAbstraction(扩充抽象类):扩充由Abstraction定义的接口,通常情况下它不再是抽象类而是具体类,它实现了在Abstraction中声明的抽象业务方法,在RefinedAbstraction中可以调用在Implementor中定义的业务方法。
Implementor(实现类接口):定义实现类的接口,这个接口不一定要与Abstraction的接口完全一致,事实上两个接口可以完全不同,一般而言,Implementor接口仅提供基本操作,而Abstraction定义的接口可能会做更多,更复杂的操作。Implementor接口对这些基本操作进行了声明,而具体实现交给其子类。通过关联关系,在Abstraction中不仅拥有自己的方法,还可以调用到Implementor中定义的方法,使用管理关系来代替继承关系
ConcreteImplementor(具体实现类):具体实现Implementor接口,在不同的ConcreteImplementor中提供基本操作的不同实现,在程序运行时,ConcreteImplementor对象将替换其父类对象,提供给抽象类具体的业务操作方法
典型代码
桥接模式是一个非常有用的设计模式,在桥接模式中体现了狠多面向对象设计原则的思想,包括单一职责原则,开闭原则,合成复用原则,里氏代换原则,依赖倒转原则等。熟悉桥接模式有助于深入理解这些设计原则,也有助于形成正确的设计思想和培养良好的设计风格。
在使用桥接模式时,首先应该识别出一个类所具有的两个独立变化的维度,将它们设计为两个独立的继承等级结构,为两个维度都提供抽象层,并建立抽象耦合。通常情况下,将具有两个独立变化维度的类的一些普通业务方法和与之关系最密切的维度设计为抽象类层次结构(抽象部分),而将另一个维度设计为实现类层次结构(实现部分),比如下图
对于毛笔而言,由于型号是其固有的维度,因此可以设计一个抽象的毛笔类,在该类中声明并部分实现毛笔的业务方法,而将各种型号的毛笔作为其子类;颜色是毛笔的另一个维度,由于它与毛笔之间存在一种‘设置’的关系,因此可以提供一个抽象的颜色接口,而将具体的颜色作为实现该接口的子类。在此,型号可以作为毛笔的抽象部分,而颜色是毛笔的实现部分。
如果需要增加一种新型号的毛笔,只需扩展左侧的抽象部分,增加一个新的扩充抽象类;如果需要增加一种新的颜色,只需扩展右侧的实现部分,增加一个新的具体实现类。扩展非常方便,无须修改已有的代码,且不会导致类的数目增长过快
对于实现部分维度,典型的实现类接口如下
interface Implementor{
public void operationImpl();
}
在实现Implementor接口的子类中实现了在该接口中声明的方法,用于定义与该维度相对应的一些具体方法。
对于另一抽象部分维度而言,其典型的抽象类代码如下
abstract class Abstraction{
protected Implementor impl; // 定义实现类接口对象
public void setImpl(Implementor impl){
this.impl = impl;
}
public abstract void operation(); //声明抽象业务方法
}
在抽象类Abstraction 中定义了一个实现类接口类型的成员impl,再通过注入的方式给该对象赋值,一般将该对象的可见性定义为protected,以便子类中访问Implementor的方法,其子类一般称为扩充对象类或细化抽象类(RefinedAbstraction),典型的RefinedAbstraction类代码如下:
class RefinedAbstraction extends Abstraction{
public void operation(){
//业务代码
impl.operationImpl(); //调用实现类的代码
//业务代码
}
}
对于客户端而言,可以针对两个维度的抽象层编程,在程序运行时再动态确定两个维度的子类,动态组合对象,将两个独立变化的维度完全解耦,以便能够灵活地扩充任一维度而对另一维度不造成任何影响
跨平台图像浏览系统
使用桥接模式设计一个可以解析不同地操作系统不同格式地图像的系统,结构图如下:
Image充当抽象类,其子类JPGImage,PNGImage,BNPImage和GIFImage充当扩充抽象类;ImageImp充当实现类接口,其子类WindowsImpl,LinuxImpl和UnixImpl充当具体实现类,完整代码如下
像素矩阵类:辅助类,各种格式的文件最终都被转化为像素矩阵,不同的操作系统提供不同的方式
//显示像素矩阵
class Matrix {
}
抽象图像类:抽象类
abstract class Image {
protected ImageImp imp;
public void setImp(ImageImp imp) {
this.imp = imp;
}
public abstract void parseFile(String fileName);
}
抽象操作系统实现类:实现类接口
interface ImageImp {
public void doPaint(Matrix m); //显示像素矩阵
}
Windows操作系统实现类:具体实现类
public class WindowsImp implements ImageImp {
@Override
public void doPaint(Matrix m) {
// TODO Auto-generated method stub
System.out.println("在windows操作系统中显示图像");
}
}
Liunx操作系统实现类:具体实现类
class LiunxImp implements ImageImp {
@Override
public void doPaint(Matrix m) {
System.out.println("在liunx操作系统中显示图像");
}
}
**Unix操作系统实现类:具体实现类 **
class UnixImp implements ImageImp{
@Override
public void doPaint(Matrix m) {
System.out.println("在Unix操作系统中显示图像");
}
}
JPG格式图像:扩充抽象类
public class JPGImage extends Image {
@Override
public void parseFile(String fileName) {
Matrix m = new Matrix();
imp.doPaint(m);
System.out.println(fileName+",格式为JPG");
}
}
PNG格式图像:扩充抽象类
public class PNGImage extends Image {
@Override
public void parseFile(String fileName) {
//模拟解析PNG文件并获得一个像素矩阵对象M
Matrix m = new Matrix();
imp.doPaint(m);
System.out.println(fileName+",格式为PNG");
}
}
BMP格式图像:扩充抽象类
public class BMPImage extends Image {
@Override
public void parseFile(String fileName) {
// 模拟解析BMP文件获得一个像素矩阵对象m
Matrix m = new Matrix();
imp.doPaint(m);
System.out.println(fileName+",格式为BMP");
}
}
GIF格式图像:扩充抽象类
public class GIFImage extends Image {
@Override
public void parseFile(String fileName) {
//模拟解析GIF文件并获得一个像素矩阵对象m
Matrix m = new Matrix();
imp.doPaint(m);
System.out.println(fileName + ",格式为GIF");
}
}
将具体扩充抽象类和具体实现类类名都存储在配置文件中
<?xml version="1.0"?>
<config>
<!-- RefinedAbstraction -->
<className>JPGImage</className>
<!-- ConcreteImplementor -->
<className>WindowsImp</className>
</config>
读取配置文件生成反射对象的XMLUtil类
public class XMLUtil {
//该方法用于从xml配置文件中提取具体类类,并返回一个实例对象
public static Object getBean(String args) {
try {
//创建文档对象
DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dFactory.newDocumentBuilder();
Document doc;
doc = builder.parse(new File("config.xml"));
NodeList nl = null;
Node classNode = null;
nl = doc.getElementsByTagName("className");
String cName = null;
if(args.equals("image")) {
//获取第一个包含类名的节点,即扩充抽象类
classNode = nl.item(0).getFirstChild();
}else if(args.equals("os")) {
//获取第二个包含类的节点,即具体实现类
classNode = nl.item(1).getFirstChild();
}
cName = classNode.getNodeValue();
//通过类名生成实例对象并将其返回
Class c = Class.forName(cName);
Object obj = c.newInstance();
return obj;
}catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
客户端测试
public class Client {
public static void main(String[] args) {
Image image;
ImageImp imp;
image = (Image) XMLUtil.getBean("image");
imp = (ImageImp) XMLUtil.getBean("os");
image.setImp(imp);
image.parseFile("小龙女");
}
}
适配器模式与桥接模式的关联
在软件开发中,适配器模式通常可以与桥接模式联合使用。适配器模式可以解决两个已有接口间不兼容问题,在这种情况下被适配的类往往是一个黑盒子,有时候用户不想也不能改变这个被适配的类,也不能控制其扩展。适配器模式通常用于现有系统与第三方产品功能的集成,采用增加适配器的方式将第三方类集成到系统中。桥接模式则不同,用户可以通过接口继承或类继承的方式来对系统进行扩展。
桥接模式和适配器模式用于设计的不同阶段。桥接模式用于系统的初步设计,对于存在两个独立变化维度的类可以将其分成抽象类与实现类两个角色,使它们可以分别进行变化;而在初步设计完成之后,当发现系统与已有类无法协同工作时,可以采用适配器模式
总结
桥接模式是设计java虚拟机和实现JDBC等驱动程序的核心模式之一,应用较为广泛。在软件开发中,如果一个类或一个系统有多个变化维度时,都可以尝试使用桥接模式对其进行设计。桥接模式为多维度变化的系统提供一套完整的解决方案,并降低系统的复杂度
主要优点
分离抽象接口及其实现部分。桥接模式使用‘对象间的关联关系’解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化(即抽象和实现不再在同一个继承层次结构中,而是‘子类化’它们,使它们各自都具有自己的子类,以便任意组合子类,从而获得多维度组合对象)
在很多情况下,桥接模式可以取代多层继承方案。多层继承方案违背了单一职责原则,复用性较差,且类的个数非常多,桥接模式是比多层继承方案更好的解决方案,它极大地减少了子类的个数
桥接模式提高了系统的可扩展性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统,符合开闭原则
缺点
桥接模式的使用会增加系统的理解与设计难度,由于关联关系建立在抽象层,要求开发者一开始就针对抽象层进行设计和编程
桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性,如何正确识别两个独立维度也需要一定的经验积累
适用场景
如果一个系统需要在抽象类和具体类之间增加更多的灵活性,避免在两个层次之间建立静态的继承关系,通过桥接模式可以使它们在抽象层建立一个关联关系
抽象部分和实现部分可以以继承的方式独立扩展而互不影响,在程序运行时可以动态地将一个抽象类子类的对象和一个实现类子类的对象进行组合,即系统需要对抽象类角色和实现类角色进行动态耦合
一个类存在两个(或多个)独立变化的维度,且这两个(或多个)维度都需要独立进行扩展
对于那些不希望使用继承或因为多层继承导致系统类的个数急剧增加的系统,桥接模式尤为适用