设计模式之桥接模式、合成/聚合复用原则的使用

1. 定义

桥接模式(Bridge):将抽象部分与它的实现部分分离,使它们都可以独立地变化

简单来说,就是把经常变化的类和经常变化的类分离,而不是用继承来耦合其关系,应该分离成两个类,降低耦合度

2. 题目引入

题目:有一个N品牌手机和一个M品牌手机,两个手机上都要有通讯录软件、一个游戏

背景:不同手机品牌的系统可能不一样,型号不一样,软件不可以共用,比如苹果手机游戏不能在安卓上玩,我们这取极端情况,所有软件在不同品牌手机上都不兼容

3. 一般解决方法

分析题目发现,有两种品牌手机N和M,所以我们可以抽象出一个抽象类即手机类,方便N和M的继承;但是发现软件在不同手机是不相同的,所以具体软件是不可以写在抽象手机类里的,要写在具体手机里,即结构图是:

设计模式之桥接模式、合成/聚合复用原则的使用
每个都是继承的关系

代码:

类:

    //智能手机父类
    class SmartPhone
    {
        public virtual void Run()
        {
            Console.WriteLine("运行");
        }
    }

    //N手机品牌
    class SmartN : SmartPhone
    {

    }

    //M手机品牌
    class SmartM : SmartPhone
    {

    }

    //N手机的使用功能通讯录
    class AddressListN : SmartN
    {
        public override void Run()
        {
            Console.WriteLine("使用N手机的通讯录");
        }
    }

    //N手机的游戏
    class GameN : SmartN
    {
        public override void Run()
        {
            Console.WriteLine("玩N手机的游戏");
        }
    }


    //M手机的使用功能通讯录
    class AddressListM : SmartM
    {
        public override void Run()
        {
            Console.WriteLine("使用M手机的通讯录");
        }
    }

    //M手机的游戏
    class GameM : SmartM
    {
        public override void Run()
        {
            Console.WriteLine("玩M手机的游戏");
        }
    }

客户端:

            SmartPhone smartPhone1 = new GameN();//N品牌的游戏
            smartPhone1.Run();

            SmartPhone smartPhone2 = new AddressListN();//N品牌的通讯录
            smartPhone2.Run();

            SmartPhone smartPhone3 = new GameM();//M品牌的游戏
            smartPhone3.Run();

            SmartPhone smartPhone4 = new AddressListM();//M品牌的通讯录
            smartPhone4.Run();

这样做结果是有的,但是,耦合度太高,继承太过严谨,不能随意扩展,一扩展就要加类,比方又有一个输入法软件的功能,那么N、M手机都要再加这个输入法的类;如果有新功能(比如登录Register())还要改变父类的功能,目前父类只有一个Run()功能,还需要在父类SmartPhone加上Register()方法,导致其他的类都要变化,如果要添加则父类一定会改变,是紧耦合,违反了开放封闭原则

及其不方便

4. 合成/聚合复用原则

合成/聚合复用原则(CARP):尽量使用合成/聚合,尽量不要使用类继承。

对该原则的理解:
合成(Composition,也有翻译成组合)和聚合(Aggregation)都是关联的特殊种类。聚合表示一种弱的‘拥有’关系,体现的是A对象可以包含B对象,但B对象不是A对象的一部分;合成则是一种强的‘拥有’关系,体现了严格的部分和整体的关系,部分和整体的生命周期一样。

比方说,大雁有两个翅膀,翅膀与大雁是部分和整体的关系,并且它们的生命周期是相同的,于是大雁和翅膀就是合成关系。而大雁是群居动物,所以每只大雁都是属于一个雁群,一个雁群可以有多只大雁,所以大雁和雁群是聚合关系。
设计模式之桥接模式、合成/聚合复用原则的使用
优点:
合成/聚合复用原则的好处是,优先使用对象的合成/聚合将有助于你保持每个类被封装,并被集中在单个任务上。这样类和类继承层次会保持较小规模,并且不太可能增长为不可控制的庞然大物。

就刚才的例子,手机是不同的品牌公司,各自做自己的软件,就像刚刚一般的设计一样,而PC却是硬件厂商做硬件,软件厂商做软件,组合起来才是可以用的机器,实际上,像‘游戏’、‘通讯录’、‘MP3音乐播放’这些功能都是软件,如果我们可以让其分离与手机的耦合,那么就可以大大减少面对新需求时改动过大的不合理情况。

解决办法其实就是应该有个‘手机品牌’抽象类和‘手机软件’抽象类,让不同的品牌和功能都分别继承于它们,这样要增加新的品牌或新的功能都不用影响其他类了。

结构图:
设计模式之桥接模式、合成/聚合复用原则的使用
现在我们需要将两个抽象类即手机品牌和手机软件结合起来,应该是手机品牌包含有手机软件,但软件并不是品牌的一部分,所以它们之间是聚合关系

结构图:
设计模式之桥接模式、合成/聚合复用原则的使用

代码:
类:

//手机软件的抽象类
    abstract class HandsetSoft
    {
        public abstract void Use();
    }
    //手机通讯录
    class AddressList : HandsetSoft
    {
        public override void Use()
        {
            Console.WriteLine("运行手机通讯录");
        }
    }
    //手机游戏
    class Game : HandsetSoft
    {
        public override void Use()
        {
            Console.WriteLine("运行手机游戏");
        }
    }

    //手机品牌的抽象类
    abstract class SmartPhone
    {
        //聚合手机软件进来,后面如果要改或者添加手机软件,不会对手机有影响,因为手机软件都是继承HandsetSoft,而手机抽象类里面有HandsetSoft类型的字段调用
        protected HandsetSoft soft1;
        public void SetHandsetSoft(HandsetSoft soft)
        {
           soft1= soft;
        }

        public abstract void Run();
    }
    //N手机品牌
    class SmartPhoneN : SmartPhone
    {
        public override void Run()
        {
            soft1.Use();
        }
    }
    //M手机品牌
    class SmartPhoneM : SmartPhone
    {
        public override void Run()
        {
            soft1.Use();
        }
    }

客户端:

	SmartPhone smartPhone1 = new SmartPhoneN();
    smartPhone1.SetHandsetSoft(new AddressList());
    smartPhone1.Run();

    SmartPhone smartPhone2 = new SmartPhoneN();
    smartPhone2.SetHandsetSoft(new Game());
    smartPhone2.Run();

    SmartPhone smartPhone3 = new SmartPhoneM();
    smartPhone3.SetHandsetSoft(new AddressList());
    smartPhone3.Run();

    SmartPhone smartPhone4 = new SmartPhoneM();
    smartPhone4.SetHandsetSoft(new Game());
    smartPhone4.Run();

这样就分离了两个可能不断变化的类,现在如果要增加一个功能,比如MP3音乐播放功能,那么只要增加这个类就行了,不会影响其他任何类,类的个数增加也只是一个

如果是要增加S品牌,只需要增加一个品牌子类就可以了,个数也是一个,不会影响其他类的改动

这显然是也符合了开放-封闭原则设计原则,这样的设计显然不会修改原来的代码,而只是扩展类就行了。但今天我的感受最深的是合成/聚合复用原则,也就是优先使用对象的合成或聚合,而不是类继承,聚合的魅力无限呀。相比,继承很容易造成不必要的麻烦。

5. 桥接模式使用例子

刚刚这个结构图,其实就是桥接模式了
设计模式之桥接模式、合成/聚合复用原则的使用
中间有一条线,连接了两个抽象类,像一条桥,所以称为桥接模式

其实,上面那个合成/聚合复用原则的例子,就是桥接模式的使用

6. 桥接模式使用结构图

设计模式之桥接模式、合成/聚合复用原则的使用
设计模式之桥接模式、合成/聚合复用原则的使用
设计模式之桥接模式、合成/聚合复用原则的使用

设计模式之桥接模式、合成/聚合复用原则的使用

7. 全部程序(例子的)

using System;

namespace 桥接模式
{
    class Program
    {
        static void Main(string[] args)
        {
            //1.按品牌分类的主程序
            //SmartPhone smartPhone1 = new GameN();//N品牌的游戏
            //smartPhone1.Run();

            //SmartPhone smartPhone2 = new AddressListN();//N品牌的通讯录
            //smartPhone2.Run();

            //SmartPhone smartPhone3 = new GameM();//M品牌的游戏
            //smartPhone3.Run();

            //SmartPhone smartPhone4 = new AddressListM();//M品牌的通讯录
            //smartPhone4.Run();

            //2.使用桥接模式的主程序

            SmartPhone smartPhone1 = new SmartPhoneN();
            smartPhone1.SetHandsetSoft(new AddressList());
            smartPhone1.Run();

            SmartPhone smartPhone2 = new SmartPhoneN();
            smartPhone2.SetHandsetSoft(new Game());
            smartPhone2.Run();

            SmartPhone smartPhone3 = new SmartPhoneM();
            smartPhone3.SetHandsetSoft(new AddressList());
            smartPhone3.Run();

            SmartPhone smartPhone4 = new SmartPhoneM();
            smartPhone4.SetHandsetSoft(new Game());
            smartPhone4.Run();

            Console.Read();
        }
    }

    //题目:有一个N品牌手机和一个M品牌手机,两个手机上都要有通讯录软件、一个游戏
    //背景:不同手机品牌的系统可能不一样,型号不一样,软件不可以共用,比如苹果手机游戏不能在安卓上玩,我们这取极端情况,所有软件在不同品牌手机上都不兼容

    //1.按品牌分类
    //提前说明下面这种方法缺点:继承太过严谨,不能随意扩展,一扩展就要加类,比方又有一个输入法软件的功能,那么N、M手机都要再加这个输入法的类;如果有新功能还要改变父类的功能,目前父类只有一个Run()功能,如果要添加则父类一定会改变,是紧耦合,违反了开放封闭原则

    //手机品牌
    //class SmartPhone
    //{
    //    public virtual void Run()
    //    {
    //        Console.WriteLine("运行");
    //    }
    //}

    //N手机品牌
    //class SmartN : SmartPhone
    //{

    //}

    //M手机品牌
    //class SmartM : SmartPhone
    //{

    //}

    //N手机的使用功能通讯录
    //class AddressListN : SmartN
    //{
    //    public override void Run()
    //    {
    //        Console.WriteLine("使用N手机的通讯录");
    //    }
    //}

    //N手机的游戏
    //class GameN : SmartN
    //{
    //    public override void Run()
    //    {
    //        Console.WriteLine("玩N手机的游戏");
    //    }
    //}


    //M手机的使用功能通讯录
    //class AddressListM : SmartM
    //{
    //    public override void Run()
    //    {
    //        Console.WriteLine("使用M手机的通讯录");
    //    }
    //}

    //M手机的游戏
    //class GameM : SmartM
    //{
    //    public override void Run()
    //    {
    //        Console.WriteLine("玩M手机的游戏");
    //    }
    //}


    //2. 按桥接模式
    //使用此模式的好处:此模式将抽象和实现分开了,即将抽象的手机品牌和实现的软件功能分开了,让其各自变化,方便了后续的修改,修改或者添加的时候不需要改变很多,如果是第一种方法纯继承,改变的就太多了

    //手机软件的抽象类
    abstract class HandsetSoft
    {
        public abstract void Use();
    }
    //手机通讯录
    class AddressList : HandsetSoft
    {
        public override void Use()
        {
            Console.WriteLine("运行手机通讯录");
        }
    }
    //手机游戏
    class Game : HandsetSoft
    {
        public override void Use()
        {
            Console.WriteLine("运行手机游戏");
        }
    }




    //手机品牌的抽象类
    abstract class SmartPhone
    {
        //聚合手机软件进来,后面如果要改或者添加手机软件,不会对手机有影响,因为手机软件都是继承HandsetSoft,而手机抽象类里面有HandsetSoft类型的字段调用
        protected HandsetSoft soft1;
        public void SetHandsetSoft(HandsetSoft soft)
        {
           soft1= soft;
        }

        public abstract void Run();
    }
    //N手机品牌
    class SmartPhoneN : SmartPhone
    {
        public override void Run()
        {
            soft1.Use();
        }
    }
    //M手机品牌
    class SmartPhoneM : SmartPhone
    {
        public override void Run()
        {
            soft1.Use();
        }
    }
}

8. 总结

桥接模式所说的‘将抽象部分与它的实现部分分离’,还是不好理解,我的理解就是实现系统可能有多角度分类,每一种分类都有可能变化,那么就把这种多角度分离出来让它们独立变化,减少它们之间的耦合。

也就是说,在发现我们需要多角度去分类实现对象,而只用继承会造成大量的类增加,不能满足开放-封闭原则时,就应该要考虑用桥接模式了。

上一篇:SmartPhone USB Sync驱动


下一篇:录制在android中安装/运行的任何应用程序的视频