设计模式的艺术之道--桥接模式

来源:互联网 发布:org.w3c.dom.node 编辑:程序博客网 时间:2024/05/19 00:16

设计模式的艺术之道–桥接模式

声明:本系列为刘伟老师博客内容总结(http://blog.csdn.net/lovelion),博客中有完整的设计模式的相关博文,以及作者的出版书籍推荐

本系列内容思路分析借鉴了刘伟老师的博文内容,同时改用C#代码进行代码的演示和分析(Java资料过多 C#表示默哀).

本系列全部源码均在文末地址给出。


本系列开始讲解结构型模式,关注如何将现有类或对象组织在一起形成更加强大的结构。
不同的结构型模式从不同的角度组合类或对象,它们在尽可能满足各种面向对象设计原则的同时为类或对象的组合提供一系列巧妙的解决方案。

  • 类结构型模式
    关心类的组合,由多个类组合成一个更大的系统,在类结构型模式中一般只存在继承关系和实现关系
  • 对象结构型模式
    关心类与对象的组合,通过关联关系,在一个类中定义另一个类的实例对象,然后通过该对象调用相应的方法

7种常见的结构型模式

这里写图片描述

桥接模式

毛笔与蜡笔的故事
这里写图片描述
蜡笔:颜色和型号两个不同的变化维度(即两个不同的变化原因)耦合在一起,无论是对颜色进行扩展还是对型号进行扩展都势必会影响另一个维度
毛笔:颜色和型号实现了分离,增加新的颜色或者型号对另一方没有任何影响

1.1定义

  • 桥接模式 (Bridge Pattern):将抽象部分与它的实现部分解耦,使得两者都能够独立变化。
  • 将类之间的静态继承关系转换为动态的对象组合关系(颜色和型号分情况继承 现在采用组合方式)
  • 一个东西有多个方向的变化,画笔 颜色和型号粗细变化 饺子 外形 (长的和小的)和肉馅(猪肉羊肉)变化

1.2情景实例

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

使用了一种多层继承结构,Image是抽象父类,而每一种类型的图像类,如BMPImage、JPGImage等作为其直接子类,不同的图像文件格式具有不同的解析方法,可以得到不同的像素矩阵;由于每一种图像又需要在不同的操作系统中显示,不同的操作系统在屏幕上显示像素矩阵有所差异,因此需要为不同的图像类再提供一组在不同操作系统显示的子类,如为BMPImage提供三个子类BMPWindowsImp、BMPLinuxImp和BMPUnixImp,分别用于在Windows、Linux和Unix三个不同的操作系统下显示图像。
UML类图
这里写图片描述
现有缺点(未来变化)
(1)由于采用了多层继承结构,导致系统中类的个数急剧增加
(2)系统扩展麻烦,由于每一个具体类既包含图像文件格式信息,又包含操作系统信息,因此无论是增加新的图像文件格式还是增加新的操作系统,都需要增加大量的具体类
如何改进
将图像文件格式(对应图像格式的解析)与操作系统(对应像素矩阵的显示)两个维度分离,使得它们可以独立变化,增加新的图像文件格式或者操作系统时都对另一个维度不造成任何影响。
引入桥接模式进行设计。
改进UML类图(中间的部分形状像一个桥 左右都是形形色色的过路人)
这里写图片描述
实例关键代码

namespace Bridge{    interface ImageImp    {        void DoPaint(Matrix m);  //显示像素矩阵m    }    class LinuxImp : ImageImp    {        public void DoPaint(Matrix m)        {            //调用Linux系统的绘制函数绘制像素矩阵            Console.Write("在Linux操作系统中显示图像:");        }    }    //其他类似省略     abstract class Image    {        protected ImageImp imp;        //注入实现类接口对象        public void SetImageImp(ImageImp imp)        {            this.imp = imp;        }        public abstract void ParseFile(string fileName);    }    class GIFImage : Image    {        public override void ParseFile(string fileName)        {            //模拟解析GIF文件并获得一个像素矩阵对象m;            Matrix m = new Matrix();             imp.DoPaint(m);            Console.WriteLine("{0},格式为GIF。", fileName);        }    }    //其他类似省略     class Program    {        static void Main(string[] args)        {            Image image;//图片格式            ImageImp imp;//系统平台            //读取配置文件            string imageType = ConfigurationManager.AppSettings["image"];            string osType = ConfigurationManager.AppSettings["os"];            //反射生成对象            image = (Image)Assembly.Load("Bridge").CreateInstance(imageType);            imp = (ImageImp)Assembly.Load("Bridge").CreateInstance(osType);            image.SetImageImp(imp);            image.ParseFile("中国地图");            Console.Read();        }    }}

1.3模式分析

动机和意图

  • 在软件开发中如何将多个变化维度分离?

一般结构

  • 适配器模式包含4个角色:
  • Abstraction(抽象类):一个主动变化的维度,通常是名词类具有其他的属性 如上边的图片
  • RefinedAbstraction(扩充抽象类)抽象类的具体泛化细分,如Gif png bmp
  • Implementor(实现类接口)另一个可能变化的维度,只是方法实现不同(也可以改成类)
  • ConcreteImplementor(具体实现类)具体实现另一个变化维度的细节类(Windos平台画 Linux平台画)

    适桥接模式UML类图
    这里写图片描述

改进的优点

  • 分离抽象接口及其实现部分
  • 可以取代多层继承方案,极大地减少了子类的个数
  • 提高了系统的可扩展性,在两个变化维度中任意扩展一个维度,不需要修改原有系统,符合开闭原则

现存的缺点

  • 会增加系统的理解与设计难度,要求开发者一开始就针对抽象层进行设计与编程
  • 正确识别出系统中两个独立变化的维度并不是一件容易的事情

优化空间
适配器模式与桥接模式的联用
在软件开发中,适配器模式通常可以与桥接模式联合使用。适配器模式通常用于现有系统与第三方产品功能的集成,采用增加适配器的方式将第三方类集成到系统中。桥接模式则不同,用户可以通过接口继承或类继承的方式来对系统进行扩展。
桥接模式和适配器模式用于设计的不同阶段,桥接模式用于系统的初步设计,对于存在两个独立变化维度的类可以将其分为抽象化和实现化两个角色,使它们可以分别进行变化;而在初步设计完成之后,当发现系统与已有类无法协同工作时,可以采用适配器模式。但有时候在设计初期也需要考虑适配器模式,特别是那些涉及到大量第三方应用接口的情况。
一个报表系统可以有多种报表显示方式也可以有多种数据采集方式,如可以从文本文件中读取数据,也可以从数据库中读取数据,还可以从Excel文件中获取数据。如果需要从Excel文件中获取数据,则需要调用与Excel相关的API,而这个API是现有系统所不具备的,该API由厂商提供。使用适配器模式和桥接模式设计该模块。
这里写图片描述

适用场景
(1) 一个类存在两个(或多个)独立变化的维度,且这两个(或多个)维度都需要独立地进行扩展。
(2)不希望使用继承或因为多层继承导致系统类的个数急剧增加的系统。
举例:冬天买手套问题 同款手套有不同的颜色 不同的材质 饭店吃火锅问题 火锅有麻辣的 微辣的 清汤的,同时又小锅和大锅两种规则

实例源代码
GitHub地址
百度云地址:链接: https://pan.baidu.com/s/1eRJv3rG 密码: w2v3

原创粉丝点击