您的位置:首页 > 编程语言 > Java开发

设计模式(8) ------- 桥接模式

2017-02-20 22:25 190 查看

设计模式(8) ——- 桥接模式

桥接模式是一种很实用的结构性设计模式。

桥接模式是一个非常有用的模式,在桥接模式中体现了很多面向对象设计原则的思想,包括“单一职责原则”、“开闭原则”、“合成复用原则”、“里氏代换原则”、“依赖倒转原则”等。熟悉桥接模式有助于我们深入理解这些设计原则,也有助于我们形成正确的设计思想和培养良好的设计风格。

概述

桥接模式

桥接模式(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对象将替换其父类对象,提供给抽象类具体的业务操作方法。

注:没看懂不要着急,先看一下案例!

案例

案例说明(跨平台图像浏览系统)

Sunny软件公司欲开发一个跨平台图像浏览系统,要求该系统能够显示BMP、JPG、GIF、PNG等多种格式的文件,并且能够在Windows、Linux、Unix等多个操作系统上运行。系统首先将各种格式的文件解析为像素矩阵(Matrix),然后将像素矩阵显示在屏幕上,在不同的操作系统中可以调用不同的绘制函数来绘制像素矩阵。系统需具有较好的扩展性以支持新的文件格式和操作系统。

Sunny软件公司的开发人员针对上述要求,提出了一个初始设计方案,如下:



分析

我们现在对该设计方案进行分析,发现存在如下两个主要问题:

(1)由于采用了多层继承结构,导致系统中类的个数急剧增加,上图中,在各种图像的操作系统实现层提供了12个具体类,加上各级抽象层的类,系统中类的总个数达到了17个,在该设计方案中,具体层的类的个数 = 所支持的图像文件格式数×所支持的操作系统数。

(2)系统扩展麻烦,由于每一个具体类既包含图像文件格式信息,又包含操作系统信息,因此无论是增加新的图像文件格式还是增加新的操作系统,都需要增加大量的具体类,例如在图中增加一种新的图像文件格式TIF,则需要增加3个具体类来实现该格式图像在3种不同操作系统的显示;如果增加一个新的操作系统Mac OS,为了在该操作系统下能够显示各种类型的图像,需要增加4个具体类。这将导致系统变得非常庞大,增加运行和维护开销。

解决方案

利用桥接模式,有两个独立变化的维度:图像文件格式和操作系统。详细见下图



代码

package 桥接模式;

/**
* 像素矩阵类
* @author kissx on 2017/2/12.
*/
public class Matrix {
private String fileName;
public Matrix(String fileName){
this.fileName = fileName;
}
}

package 桥接模式;

/**
* 实现部分接口
* @author kissx on 2017/2/12.
*/
public interface ImageImp {
void doPaint(Matrix matrix);
}

package 桥接模式;

/**
* 抽象部分接口类(抽象类)
* @author kissx on 2017/2/12.
*/
public abstract class Image {
protected ImageImp imageImp;
public final void setImageImp(ImageImp imageImp){
this.imageImp = imageImp;
}
public abstract void paint(String fileName);
}

package 桥接模式;

/**
* @author kissx on 2017/2/12.
*/
public class BMPImage extends Image {
@Override
public void paint(String fileName) {
imageImp.doPaint(new Matrix(fileName));
System.out.println(fileName + ".bmp 显示成功!");
}
}

package 桥接模式;

/**
* @author kissx on 2017/2/12.
*/
public class JPGImage extends Image {
@Override
public void paint(String fileName) {
imageImp.doPaint(new Matrix(fileName));
System.out.println(fileName + ".JPG 显示成功!");
}
}

package 桥接模式;

/**
* @author kissx on 2017/2/12.
*/
public class PNGImage extends Image {
@Override
public void paint(String fileName) {
imageImp.doPaint(new Matrix(fileName));
System.out.println(fileName + ".png 显示成功!");
}
}

package 桥接模式;

/**
* @author kissx on 2017/2/12.
*/
public class GIFImage extends Image {
@Override
public void paint(String fileName) {
imageImp.doPaint(new Matrix(fileName));
System.out.println(fileName + ".gif 显示成功!");
}
}

package 桥接模式;

/**
* @author kissx on 2017/2/12.
*/
public class LinuxImp implements ImageImp {
@Override
public void doPaint(Matrix matrix) {
System.out.println("Linux 上显示图片!");
}
}

package 桥接模式;

/**
* @author kissx on 2017/2/12.
*/
public class WindowsImp implements ImageImp {
@Override
public void doPaint(Matrix matrix) {
System.out.println("Windows 上显示图片!");
}
}

package 桥接模式;

/**
* @author kissx on 2017/2/12.
*/
public class UnixImp implements ImageImp {
@Override
public void doPaint(Matrix matrix) {
System.out.println("Unix 上显示图片!");
}
}

package 桥接模式;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;

/**
* 为了实现动态耦合,这里读取了 xml 配置文件,此类为读取的工具类
* @author kissx on 2017/2/12.
*/
public class XMLUtil {
public static Object getBean(String args){
try{
DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dFactory.newDocumentBuilder();
Document doc = builder.parse(new File("D:\\project\\DesignPattern\\src\\桥接模式\\config.xml"));
NodeList nl = doc.getElementsByTagName("className");
Node node = null;
String cName;
if (args.equals("image")){
node = nl.item(0).getFirstChild();
}else if (args.equals("io")){
node = nl.item(1).getFirstChild();
}
cName = node != null ? node.getNodeValue() : null;
return Class.forName("桥接模式." + cName).newInstance();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}

<?xml version="1.0" encoding="UTF-8" ?>
<config>
<className>JPGImage</className>
<className>LinuxImp</className>
</config>


总结

桥接模式的优点

(1)分离抽象接口及其实现部分。桥接模式使用“对象间的关联关系”解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化。所谓抽象和实现沿着各自维度的变化,也就是说抽象和实现不再在同一个继承层次结构中,而是“子类化”它们,使它们各自都具有自己的子类,以便任何组合子类,从而获得多维度组合对象。+

(2)在很多情况下,桥接模式可以取代多层继承方案,多层继承方案违背了“单一职责原则”,复用性较差,且类的个数非常多,桥接模式是比多层继承方案更好的解决方法,它极大减少了子类的个数。

(3)桥接模式提高了系统的可扩展性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统,符合“开闭原则”。

适用场合

(1)如果一个系统需要在抽象化和具体化之间增加更多的灵活性,避免在两个层次之间建立静态的继承关系,通过桥接模式可以使它们在抽象层建立一个关联关系。

(2)“抽象部分”和“实现部分”可以以继承的方式独立扩展而互不影响,在程序运行时可以动态将一个抽象化子类的对象和一个实现化子类的对象进行组合,即系统需要对抽象化角色和实现化角色进行动态耦合。

(3)一个类存在两个(或多个)独立变化的维度,且这两个(或多个)维度都需要独立进行扩展。

(4)对于那些不希望使用继承或因为多层继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。

本文部分内容来自 《设计模式 Java版》,感谢!

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息