推荐一读的概述
毛笔和蜡笔是两种很常见的文具,它们都归属于画笔。假设需要大、中、小 3 种型号的画笔,能够绘制 12 种不同的颜色,如果使用蜡笔,需要准备 3 × 12 = 36 支,但是如果使用毛笔,只需要提供 3 种型号的毛笔,外加一个包含 12 种颜色的调色板,涉及的对象个数仅为 3 + 12 = 15,远小于 36 ,却能够实现 36 支蜡笔同样的功能。如果增加一种新型号的画笔,并且也需要具有 12 种颜色,对应的蜡笔需要增加 12 支,而毛笔只需要增加 1 支。
不难得知,在蜡笔中,颜色和型号两个不同的变化维度耦合在一起,无论是对颜色进行扩展还是对型号进行扩展势必会影响另一个维度;但在毛笔中,颜色和型号实现了分离,增加新的颜色或者型号对另一方没有任何影响。如果使用软件工程中的术语,可以认为在蜡笔中颜色和型号之间存在较强的耦合性,而毛笔很好地将二者解耦,使用起来更加的灵活,扩展也更为方便。在软件开发中有一种设计模式可以用来处理与画笔类似的具有多变化维度的情况,这就是 桥接模式 。
在桥接模式中将两个独立变化的维度设计为两个独立的继承等级结构,而不是将二者耦合在一起形成多层继承结构。桥接模式在抽象层建立起一个抽象关联,该关联关系类似一条连接两个独立继承结构的桥,故名桥接模式。
桥接模式:将抽象部分与它的实现部分解耦,使得两者都能够独立变化。
桥接模式是一种对象结构型模式,它又被称为柄体(Handle and Body)模式或接口(Interface)模式。桥接模式用一种巧妙地方式处理多层继承存在的问题,用抽象关系取代了传统的多层继承,将类之间的静态继承关系转换为动态的对象组合关系,使得系统更加灵活,并易于扩展,同时有效地控制了系统中类的个数。
桥接模式包含以下 4 个角色:
某软件公司要开发一个跨平台图像浏览系统,要求该系统能够显示 BMP、JPG、GIF、PNG 等多种格式的文件,并且能够在 Windows、Linux、UNIX 等多个操作系统上运行。系统首先将各种格式的文件解析为像素矩阵(Matrix),然后将像素矩阵显示在屏幕上,在不同的操作系统中可以调用不同的绘制函数来绘制像素矩阵。系统需要具有较好的扩展性,以便在将来支持新的文件格式和操作系统。请使用桥接模式。
import StructuralPattern.BridgePattern.Implementator.ImageImpl; public abstract class Image { protected ImageImpl imageImpl; //注入实现类接口对象 public void setImageImpl(ImageImpl imageImpl) { this.imageImpl = imageImpl; } public abstract void parseFile(String fileName); } 复制代码
import StructuralPattern.BridgePattern.Abstraction.Image; import StructuralPattern.BridgePattern.Matrix; public class BMPImage extends Image { @Override public void parseFile(String fileName) { //模拟解析 BMP 文件并获得一个像素矩阵对象 m Matrix m = new Matrix(); imageImpl.doPaint(m); System.out.println(fileName + ",格式为 BMP 。"); } } 复制代码
import StructuralPattern.BridgePattern.Abstraction.Image; import StructuralPattern.BridgePattern.Matrix; public class JPGImage extends Image { @Override public void parseFile(String fileName) { //模拟解析 JPG 文件并获得一个像素矩阵对象 m Matrix m = new Matrix(); imageImpl.doPaint(m); System.out.println(fileName + ",格式为 JPG 。"); } } 复制代码
import StructuralPattern.BridgePattern.Abstraction.Image; import StructuralPattern.BridgePattern.Matrix; public class GIFImage extends Image { @Override public void parseFile(String fileName) { //模拟解析 GIF 文件并获得一个像素矩阵对象 m Matrix m = new Matrix(); imageImpl.doPaint(m); System.out.println(fileName + ",格式为 GIF 。"); } } 复制代码
import StructuralPattern.BridgePattern.Abstraction.Image; import StructuralPattern.BridgePattern.Matrix; public class PNGImage extends Image { @Override public void parseFile(String fileName) { //模拟解析 PNG 文件并获得一个像素矩阵对象 m Matrix m = new Matrix(); imageImpl.doPaint(m); System.out.println(fileName + ",格式为 PNG 。"); } } 复制代码
import StructuralPattern.BridgePattern.Matrix; public interface ImageImpl { public void doPaint(Matrix m); } 复制代码
import StructuralPattern.BridgePattern.Implementator.ImageImpl; import StructuralPattern.BridgePattern.Matrix; public class WindowsImpl implements ImageImpl { @Override public void doPaint(Matrix m) { System.out.print("在 Windows 操作系统中显示图像:"); } } 复制代码
import StructuralPattern.BridgePattern.Implementator.ImageImpl; import StructuralPattern.BridgePattern.Matrix; public class LinuxImpl implements ImageImpl { @Override public void doPaint(Matrix m) { System.out.print("在 Linux 操作系统中显示图像:"); } } 复制代码
import StructuralPattern.BridgePattern.Implementator.ImageImpl; import StructuralPattern.BridgePattern.Matrix; public class UnixImpl implements ImageImpl { @Override public void doPaint(Matrix m) { System.out.print("在 UNIX 操作系统中显示图像:"); } } 复制代码
public class Matrix { } 复制代码
<?xml version="1.0" encoding="UTF-8"?> <config> <!--RefinedAbstraction--> <className>StructuralPattern.BridgePattern.RefinedAbstraction.JPGImage</className> <!--ConcreteImplementor--> <className>StructuralPattern.BridgePattern.ConcreteImplementor.WindowsImpl</className> </config> 复制代码
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; public class XMLUtil { public static Object getBean(String args) { try { //todo:创建 DOM 文档对象 DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); Document doc; doc = documentBuilder.parse("src//StructuralPattern//BridgePattern//config.xml"); //todo:获取包含类名的文本节点 NodeList nodeList = null; Node classNode = null; String cName = null; nodeList = doc.getElementsByTagName("className"); if (args.equals("image")) { //todo:获取第一个包含类名的节点,即扩充抽象类 classNode = nodeList.item(0).getFirstChild(); } else if (args.equals("os")) { //todo:获取第二个包含类名的节点,即具体实现类 classNode = nodeList.item(1).getFirstChild(); } cName = classNode.getNodeValue(); //todo:通过类名生成实例对象并将其返回 Class clz = Class.forName(cName); Object obj = clz.newInstance(); return obj; } catch (Exception e) { e.printStackTrace(); return null; } } } 复制代码
import StructuralPattern.BridgePattern.Abstraction.Image; import StructuralPattern.BridgePattern.Implementator.ImageImpl; public class Client { public static void main(String[] args) { Image image; ImageImpl imageImpl; image = (Image) XMLUtil.getBean("image"); imageImpl = (ImageImpl) XMLUtil.getBean("os"); image.setImageImpl(imageImpl); image.parseFile("小龙女"); } } 复制代码
从举例容易看出,客户端面向抽象编程,每个维度都有一个高级抽象类,每个维度的具体选项继承自该高级抽象类。举例中我们通过 xml 配置文件配置各个维度的选择,选择什么类型的图片?选择什么操作系统?实现了不同维度的的解耦。
桥接模式十设计 Java 虚拟机和实现 JDBC 等驱动程序的核心模式之一,应用较为广泛。在软件开发中如果一个类或一个系统有多个变化维度都可以尝试使用桥接模式对其进行设计。桥接模式为多维度变化的系统提供了一套完整的解决方案,并且降低了系统的复杂度。