//像素矩阵类:辅助类,各种格式的文件最终都被转化为像素矩阵,不同的操作系统提供不同 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类的代码如下所示:
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。
桥接模式总结
桥接模式是设计Java虚拟机和实现JDBC等驱动程序的核心模式之一,应用较为广泛。在软件开发中如果一个类或一个系统有多个变化维度时,都可以尝试使用桥接模式对其进行设计。桥接模式为多维度变化的系统提供了一套完整的解决方案,并且降低了系统的复杂度。
桥接模式的主要优点如下:
(1) 分离抽象接口及其实现部分。桥接模式使用“对象间的关联关系”解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化。所谓抽象和实现沿着各自维度的变化,也就是说抽象和实现不再在同一个继承层次结构中,而是“子类化”它们,使它们各自都具有自己的子类,以便任何组合子类,从而获得多维度组合对象。
(2) 在很多情况下,桥接模式可以取代多层继承方案,多层继承方案违背了“单一职责原则”, 复用性较差,且类的个数非常多,桥接模式是比多层继承方案更好的解决方法,它极大减少了子类的个数。
(3) 桥接模式提高了系统的可扩展性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统,符合“开闭原则”。
桥接模式的主要缺点如下:
(1) 桥接模式的使用会增加系统的理解与设计难度,由于关联关系建立在抽象层,要求开发者一开始就针对抽象层进行设计与编程。
(2) 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性,如何正确识别两个独立维度也需要一定的经验积累。
在以下情况下可以考虑使用桥接模式:
(1) 如果一个系统需要在抽象化和具体化之间增加更多的灵活性,避免在两个层次之间建立静态的继承关系,通过桥接模式可以使它们在抽象层建立一个关联关系。
(2) “抽象部分”和“实现部分”可以以继承的方式独立扩展而互不影响,在程序运行时可以动态将一个抽象化子类的对象和一个实现化子类的对象进行组合,即系统需要对抽象化角色和实现化角色进行动态耦合。
(3) 一个类存在两个(或多个)独立变化的维度,且这两个(或多个)维度都需要独立进行扩展。
(4) 对于那些不希望使用继承或因为多层继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。