桥接模式的定义与特点
桥接(Bridge)模式的定义如下:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
桥接(Bridge)模式的优点是:
由于抽象与实现分离,所以扩展能力强;
其实现细节对客户透明。
缺点是:由于聚合关系建立在抽象层,要求开发者针对抽象化进行设计与编程,这增加了系统的理解与设计难度。
桥接模式的结构与实现
模式的结构
桥接模式是一种很实用的结构型设计模式,如果软件系统中某个类存在两个独立变化的维度,通过该模式可以将这两个维度分离出来,使两者可以独立扩展,让系统更加符合“单一职责原则”。与多层继承方案不同,它将两个独立变化的维度设计为两个独立的继承等级结构,并且在抽象层建立一个抽象关联,该关联关系类似一条连接两个独立继承结构的桥,故名桥接模式。桥接模式用一种巧妙的方式处理多层继承存在的问题,用抽象关联取代了传统的多层继承,将类之间的静态继承关系转换为动态的对象组合关系,使得系统更加灵活,并易于扩展,同时有效控制了系统中类的个数。桥接定义如下:桥接模式(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对象将替换其父类对象,提供给抽象类具体的业务操作方法。桥接模式是一个非常有用的模式,在桥接模式中体现了很多面向对象设计原则的思想,包括“单一职责原则”、“开闭原则”、“合成复用原则”、“里氏代换原则”、“依赖倒转原则”等。熟悉桥接模式有助于我们深入理解这些设计原则,也有助于我们形成正确的设计思想和培养良好的设计风格。
模式的实现
package bridge;
public class BridgeTest
{
public static void main(String[] args)
{
Implementor imple=new ConcreteImplementorA();
Abstraction abs=new RefinedAbstraction(imple);
abs.Operation();
}
}
//实现化角色
interface Implementor
{
public void OperationImpl();
}
//具体实现化角色
class ConcreteImplementorA implements Implementor
{
public void OperationImpl()
{
System.out.println("具体实现化(Concrete Implementor)角色被访问" );
}
}
//抽象化角色
abstract class Abstraction
{
protected Implementor imple;
protected Abstraction(Implementor imple)
{
this.imple=imple;
}
public abstract void Operation();
}
//扩展抽象化角色
class RefinedAbstraction extends Abstraction
{
protected RefinedAbstraction(Implementor imple)
{
super(imple);
}
public void Operation()
{
System.out.println("扩展抽象化(Refined Abstraction)角色被访问" );
imple.OperationImpl();
}
}
应用实例
欲开发一个跨平台图像浏览系统,要求该系统能够显示BMP、JPG、GIF、PNG等多种格式的文件,并且能够在Windows、Linux、Unix等多个操作系统上运行。系统首先将各种格式的文件解析为像素矩阵(Matrix),然后将像素矩阵显示在屏幕上,在不同的操作系统中可以调用不同的绘制函数来绘制像素矩阵。系统需具有较好的扩展性以支持新的文件格式和操作系统
为了减少所需生成的子类数目,实现将操作系统和图像文件格式两个维度分离,使它们可以独立改变,Sunny公司开发人员使用桥接模式来重构跨平台图像浏览系统的设计,其基本结构。
Image充当抽象类,其子类JPGImage、PNGImage、BMPImage和GIFImage充当扩充抽象类;ImageImp充当实现类接口,其子类WindowsImp、LinuxImp和UnixImp充当具体实现类。
完整代码如下所示:
//像素矩阵类:辅助类,各种格式的文件最终都被转化为像素矩阵,不同的操作系统提供不同的方式显示像素矩阵
class Matrix {
//此处代码省略
}
//抽象图像类:抽象类
abstract class Image {
protected ImageImp imp;
public void setImageImp(ImageImp imp) {
this.imp = imp;
}
public abstract void parseFile(String fileName);
}
//抽象操作系统实现类:实现类接口
interface ImageImp {
public void doPaint(Matrix m); //显示像素矩阵m
}
//Windows操作系统实现类:具体实现类
class WindowsImp implements ImageImp {
public void doPaint(Matrix m) {
//调用Windows系统的绘制函数绘制像素矩阵
System.out.print("在Windows操作系统中显示图像:");
}
}
//Linux操作系统实现类:具体实现类
class LinuxImp implements ImageImp {
public void doPaint(Matrix m) {
//调用Linux系统的绘制函数绘制像素矩阵
System.out.print("在Linux操作系统中显示图像:");
}
}
//Unix操作系统实现类:具体实现类
class UnixImp implements ImageImp {
public void doPaint(Matrix m) {
//调用Unix系统的绘制函数绘制像素矩阵
System.out.print("在Unix操作系统中显示图像:");
}
}
//JPG格式图像:扩充抽象类
class JPGImage extends Image {
public void parseFile(String fileName) {
//模拟解析JPG文件并获得一个像素矩阵对象m;
Matrix m = new Matrix();
imp.doPaint(m);
System.out.println(fileName + ",格式为JPG。");
}
}
//PNG格式图像:扩充抽象类
class PNGImage extends Image {
public void parseFile(String fileName) {
//模拟解析PNG文件并获得一个像素矩阵对象m;
Matrix m = new Matrix();
imp.doPaint(m);
System.out.println(fileName + ",格式为PNG。");
}
}
//BMP格式图像:扩充抽象类
class BMPImage extends Image {
public void parseFile(String fileName) {
//模拟解析BMP文件并获得一个像素矩阵对象m;
Matrix m = new Matrix();
imp.doPaint(m);
System.out.println(fileName + ",格式为BMP。");
}
}
//GIF格式图像:扩充抽象类
class GIFImage extends Image {
public void parseFile(String fileName) {
//模拟解析GIF文件并获得一个像素矩阵对象m;
Matrix m = new Matrix();
imp.doPaint(m);
System.out.println(fileName + ",格式为GIF。");
}
}
为了让系统具有更好的灵活性和可扩展性,我们引入了配置文件,将具体扩充抽象类和具体实现类类名都存储在配置文件中,再通过反射生成对象,将生成的具体实现类对象注入到扩充抽象类对象中,其中,配置文件config.xml的代码如下所示:
<?xml version="1.0"?>
<config>
<!--RefinedAbstraction-->
<className>JPGImage</className>
<!--ConcreteImplementor-->
<className>WindowsImp</className>
</config>
用于读取配置文件config.xml并反射生成对象的XMLUtil类的代码如下所示:
import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import java.io.*;
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;
String cName=null;
nl = doc.getElementsByTagName("className");
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;
}
}
}
编写如下客户端测试代码:
class Client {
public static void main(String args[]) {
Image image;
ImageImp imp;
image = (Image)XMLUtil.getBean("image");
imp = (ImageImp)XMLUtil.getBean("os");
image.setImageImp(imp);
image.parseFile("小龙女");
}
}
编译并运行程序,输出结果如下:
在Windows操作系统中显示图像:小龙女,格式为JPG
桥接模式的应用场景
桥接模式通常适用于以下场景。
当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时。
当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时。
当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时。
桥接模式模式的扩展
在软件开发中,有时桥接(Bridge)模式可与设配器模式联合使用。当桥接(Bridge)模式的实现化角色的接口与现有类的接口不一致时,可以在二者中间定义一个适配器将二者连接起来,其具体结构图如图: