C#:桥接模式

转自原文 C#:桥接模式

1.引言

桥接模式是一种很实用的结构型设计模式,如果系统中的某各类存在两个独立变化的纬度,通过桥接模式可以将这两个纬度分离出来,它是将抽象部分和实现部分解耦,使得两者都能够独立变化。如果不理解,怎么说都会觉得晦涩,看下面的两个例子:
(1)示例1【桥接模式
把遥控器做为一个抽象类,抽象类中提供遥控器的所有实现,其他具体电视品牌的遥控器都继承这个抽象类,具体设计类图如下:
桥接模式
这样的实现使得每部不同型号的电视都有自己遥控器实现,这样的设计对于电视机的改变可以很好地应对,只需要添加一个派生类就搞定了,但随着时间的推移,用户需要改变遥控器的功能,如:用户可能后面需要对遥控器添加返回上一个台等功能时,此时上面的设计就需要修改抽象类RemoteControl的提供的接口了,此时可能只需要向抽象类中添加一个方法就可以解决了,但是这样带来的问题是我们改变了抽象的实现,如果用户需要同时改变电视机品型号和遥控器功能时,上面的设计就会导致相当大的修改,显然这样的设计并不是好的设计。
(2)示例2
画笔有毛笔和蜡笔两种,假如需要大、中、小3种型号的画笔,能够分别绘制出12种不同的颜色,如果使用蜡笔的话需要准备36支,如果使用毛笔,则只需要准备3种型号的毛笔外加12种颜色的调色板,总共15样。如果需要新增一种新型号的画笔,对于蜡笔来说需要增加12支,而对于毛笔来说只需要增加1支即可。可以认为在蜡笔中颜色和型号之间具有较强的耦合性,而毛笔能够很好地将二者解耦,使用起来非常灵活,这种将抽象和实现解耦分离的模式在软件设计模式中称为桥接模式。

毛笔桥接模式示意图

2.桥接模式

桥接模式
(1)Abstraction(抽象类):它是用于定义抽象接口,通常是抽象类而不是接口,其中定义了一个Implementor(实现接口)类型的对象并可以维护该对象,它与Implementor之间具有关联关系。
(2)RefinedAbstraction(扩充抽象类):通常是具体类,实现了Abstraction中的抽象方法,在RefinedAbstraction中可以调用Implementor中定义的方法。
(3)Implementor(实现类接口):通常情况下提供一些基本操作的声明,将具体实现交给其子类。
(4)ConcreteImplementor(具体实现类):实现了Implementor种定义的方法,运行时根据里氏替换原则,ConcreteImplementor对象将替换其父类对象,提供给抽象对象类具体的业务操作方法。

3.桥接模式应用实例

(1)实例说明
某软件公司开发一个跨平台图像浏览系统,要求改系统能够显示BMP、JPG、GIF、PNG等多种格式的文件,并且能够在window、Linux、UNIX等多个操作系统上运行。系统首先将各种文件解析为像素矩阵,然后将像素举证显示在屏幕上,在不同的操作系统中可以调用不同的绘制函数来绘制像素矩阵。另外系统需要较好的扩展性,以便支持新的文件格式和操作系统。现使用桥接模式来实际跨平台的图像浏览系统。
(2)实例UML
图片浏览
(3)实现代码

    /// <summary>
    /// 像素矩阵,辅助类
    /// </summary>
    class Matrix
    {
        //代码省略
    }

    /// <summary>
    /// 抽象操作系统实现类
    /// </summary>
    interface ImageImp
    {
        void DoPaint(Matrix m);//显示像素矩阵m
    }

    /// <summary>
    /// Windows操作系统实现类,充当具体实现类
    /// </summary>
    class WindowsImp:ImageImp
    {
        public void DoPaint(Matrix m)
        {
            Console.WriteLine("在Windows操作系统中显示图像");
        }
    }

    /// <summary>
    /// Unix操作系统实现类,充当具体实现类
    /// </summary>
    class UnixImp:ImageImp
    {
        public void DoPaint(Matrix m)
        {
            Console.WriteLine("在Unix操作系统中显示图像");
        }
    }

    /// <summary>
    /// Linux操作系统实现类,充当具体实现类
    /// </summary>
    class LinuxImp:ImageImp
    {
        public void DoPaint(Matrix m)
        {
            Console.WriteLine("在Linux操作系统中显示图像");
        }
    }

    /// <summary>
    /// 抽象图像类,充当抽象类
    /// </summary>
    abstract class Image
    {
        protected ImageImp imp;

        public void SetImageImp(ImageImp imp)
        {
            this.imp = imp;
        }

        public abstract void ParseFile(string fileName);
    }

    /// <summary>
    /// JPG格式图像类,充当扩充抽象类
    /// </summary>
    class JPGImage:Image
    {
        public override void ParseFile(string fileName)
        {
            Matrix m = new Matrix();
            imp.DoPaint(m);
            Console.WriteLine("{0},格式为JPG", fileName);
        }
    }

    /// <summary>
    /// PNG格式图像类,充当扩充抽象类
    /// </summary>
    class PNGImage:Image
    {
        public override void ParseFile(string fileName)
        {
            Matrix m = new Matrix();
            imp.DoPaint(m);
            Console.WriteLine("{0},格式为PNG", fileName);
        }
    }

    /// <summary>
    /// BMP格式图像类,充当扩充抽象类
    /// </summary>
    class BMPImage:Image
    {
        public override void ParseFile(string fileName)
        {
            Matrix m = new Matrix();
            imp.DoPaint(m);
            Console.WriteLine("{0},格式为BMP",fileName);
        }
    }

    /// <summary>
    /// GIF格式图像类,充当扩充抽象类
    /// </summary>
    class GIFImapg:Image
    {
        public override void ParseFile(string fileName)
        {
            Matrix m = new Matrix();
            imp.DoPaint(m);
            Console.WriteLine("{0},格式为GIF", fileName);
        }
    }

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>
  <appSettings>
    <add key="image" value="PictureBrowser.JPGImage"/>
    <add key="os" value="PictureBrowser.WindowsImp"/>
  </appSettings>
</configuration>

    /// <summary>
    /// 客户端测试类
    /// </summary>
    class Program
    {
        static void Main(string[] args)
        {
            Image image;
            ImageImp imp;

            string imageType = ConfigurationManager.AppSettings["image"];
            string osType = ConfigurationManager.AppSettings["os"];

            image = Assembly.Load("PictureBrowser").CreateInstance(imageType) as Image;
            imp = Assembly.Load("PictureBrowser").CreateInstance(osType) as ImageImp;

            image.SetImageImp(imp);
            image.ParseFile("中国地图");
            Console.ReadKey();
        }
    }

 


测试结果:
测试结果

4.桥接模式的优缺点

(1)桥接模式的优点
a)分离抽象接口及其实现部分。桥接模式使用“对象间的关联关系”解耦了抽象和实现之间固有的绑定关系,使得抽象和实现沿着各自的纬度变化。所谓抽象和实现沿着各自维度的变化,也就是说抽象和实现不再在同一个继承层次结构中,而是“子类化”它们,使得它们各自具有自己的子类,以便任意组合子类,从而获得更多维度的组合对象。
b)在很多情况下,桥接模式可以取代多层继承方案,多层继承方案违背了单一职责原则,复用性较差,且类的个数非常多。桥接模式是比多层继承方案更好的解决方案,它极大地减少了子类的个数。
c)桥接模式提高了系统的可扩展性,在两个变化维度之间扩展一个维度,不需要修改原有系统,符合开闭原则。

(2)桥接模式的缺点
a)桥接模式的使用会增加系统的理解与设计难度,由于关联关系建立在抽象层,要求开发者一开始就针对抽象层进行设计与编程。
b)桥接模式要求正确识别出系统中两个独立变化的纬度,因此其使用范围具有一定的局限性,如何识别两个独立变化的维度也需要一定的经验积累。

(3)桥接模式的适用环境 a)如果一个系统需要在抽象化和具体化之间增加更多的灵活性,避免在两个层次之间建立静态的继承关系,通过桥接模式可以使它们在抽象层建立一个关联关系。 b)抽象部分和实现部分可以以继承的方式独立扩展而互不影响,在程序运行时可以动态地将一个抽象化子类对象和一个实现化子类对象进行组合,即是系统需要对抽象化角色和实现化的角色进行动态解耦。 c)一个类存在两个(或者多个)独立变化的维度,且这两个(或者多个)维度需要独立进行扩展。 d)对于不希望使用继承或者因为多继承导致系统类的个数急剧增加的系统,桥接模式尤为使用。

 

 

 

posted @ 2017-05-26 16:55  wenglabs  阅读(1135)  评论(0编辑  收藏  举报