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. 总结
桥接模式所说的‘将抽象部分与它的实现部分分离’,还是不好理解,我的理解就是实现系统可能有多角度分类,每一种分类都有可能变化,那么就把这种多角度分离出来让它们独立变化,减少它们之间的耦合。
也就是说,在发现我们需要多角度去分类实现对象,而只用继承会造成大量的类增加,不能满足开放-封闭原则时,就应该要考虑用桥接模式了。