桥接模式(21)
今天我们来讲一下桥接模式。
一、案例
我有N牌子的一个手机,需要运行一款游戏软件。咱们用简单的控制台应用程序来实现一下。
1 /// <summary> 2 /// N品牌的手机中的游戏 3 /// </summary> 4 class HandsetNGame 5 { 6 public void Run() 7 { 8 Console.WriteLine("运行N品牌手机游戏"); 9 } 10 }
客户端调用:
1 public static void Main() 2 { 3 HandsetNGame n = new HandsetNGame(); 4 n.Run(); 5 Console.ReadKey(); 6 }
二、演绎
1、第一步演绎:
如果我不仅有N品牌的手机,还有M品牌的手机也需要运行这款游戏软件,怎么办?
我们可以将运行游戏软件抽象出一个父类,让N,M品牌的手机继承这个父类。代码如下:
1 /// <summary> 2 /// 父类 3 /// </summary> 4 class HandsetGame 5 { 6 public virtual void Run() 7 { 8 9 } 10 } 11 /// <summary> 12 /// 子类 13 /// </summary> 14 class HandsetNGame : HandsetGame 15 { 16 public override void Run() 17 { 18 Console.WriteLine("运行N品牌手机游戏"); 19 } 20 } 21 /// <summary> 22 /// 子类 23 /// </summary> 24 class HandsetMGame : HandsetGame 25 { 26 public override void Run() 27 { 28 Console.WriteLine("运行M品牌手机游戏"); 29 } 30 }
客户端调用:
1 public static void Main() 2 { 3 HandsetGame gameM = new HandsetMGame(); 4 HandsetGame gameN = new HandsetNGame(); 5 gameM.Run(); 6 gameN.Run(); 7 Console.ReadKey(); 8 }
2、第二步演绎
还有一个问题是,手机不仅能玩游戏,还要有通讯录的功能,也就是说N,M品牌的手机都有通讯录的功能,那么我们该怎么做呢?
小伙伴们说,这容易啊,跟第一步一样,抽象出一个通讯录的父类,让N,M品牌手机通讯录继承这个父类即可。代码如下:
1 /// <summary> 2 /// 父类 3 /// </summary> 4 class HandsetBrand 5 { 6 public virtual void Run() 7 { 8 9 } 10 } 11 /// <summary> 12 /// 子类 13 /// </summary> 14 class HandNsetBrand:HandsetBrand 15 { 16 public override void Run() 17 { 18 Console.WriteLine("运行N品牌手机通讯录"); 19 } 20 } 21 /// <summary> 22 /// 子类 23 /// </summary> 24 class HandMsetBrand:HandsetBrand 25 { 26 public override void Run() 27 { 28 Console.WriteLine("运行M品牌手机通讯录"); 29 } 30 }
客户端调用是类似的,在此不写代码了。
好,那么问题来了,我们手机不仅有游戏,通讯录功能,还有很多很多的功能,例如,照相,彩信等等等等,照这样下去,我们这种设计将会让类异常的多,项目异常的庞大,出现这种情况,我们不禁怀疑我们的这种设计是不是出现了问题,如果出现了问题,我们改用什么方式解决呢?
对象的继承关系是在编译时就定义好了,所以无法在运行时改变从父类继承的实现,子类的实现与它的父类有非常紧密的依赖关系,以至于父类实现中的任何变化必然会导致子类发生变化。当你需要复用子类时,如果继承下来的实现不适合解决新的问题,则父类必须重写或者被其他更适合的类替换,这种依赖关系限制了灵活性并最终限制了复用性。
在这里首先我们来讲一个设计原则,合成/聚合复用原则。
合成/聚合复用原则,尽量使用合成/聚合尽量不要使用类继承,聚合表示一种弱的‘拥有’关系,体现的是A对象可以包含B对象,但B对象不是A对象的一部分;合成表示一种强的‘拥有’关系,体现了严格的部分和整体的关系,部分和整体的生命周期一样。打个比方,大雁有两只翅膀,翅膀和大雁就是部分和整体的关系,他们的生命周期是相同的,是合成。大雁和雁群,是聚合关系。
好,本着合成/聚合复用原则,我们重新来设计一下我们案例中的代码
1 //手机软件 2 abstract class HandsetSoft 3 { 4 public abstract void Run(); 5 } 6 //手机游戏 7 class HandsetGame:HandsetSoft 8 { 9 public override void Run() 10 { 11 Console.WriteLine("运行手机游戏"); 12 } 13 } 14 //手机通讯录 15 class HandsetAddressList:HandsetSoft 16 { 17 public override void Run() 18 { 19 Console.WriteLine("运行手机通讯录"); 20 } 21 } 22 //手机品牌 23 abstract class HandsetBrand 24 { 25 protected HandsetSoft soft; 26 //品牌需要关注软件,所以可在机器中安装软件,以备运行 27 public void SetHandsetSoft(HandsetSoft soft) 28 { 29 this.soft = soft; 30 } 31 //运行 32 public abstract void Run(); 33 } 34 //具体的手机品牌N 35 class HandsetBrandN:HandsetBrand 36 { 37 public override void Run() 38 { 39 soft.Run(); 40 } 41 } 42 //具体的手机品牌M 43 class HandsetBrandM:HandsetBrand 44 { 45 public override void Run() 46 { 47 soft.Run(); 48 } 49 }
客户端调用:
1 public static void Main() 2 { 3 HandsetBrand ab; 4 ab = new HandsetBrandN(); 5 ab.SetHandsetSoft(new HandsetGame()); 6 ab.Run(); 7 8 ab.SetHandsetSoft(new HandsetAddressList()); 9 ab.Run(); 10 11 ab = new HandsetBrandM(); 12 ab.SetHandsetSoft(new HandsetGame()); 13 ab.Run(); 14 15 ab.SetHandsetSoft(new HandsetAddressList()); 16 ab.Run(); 17 Console.ReadKey(); 18 }
以上就是我们要将的一种设计模式:桥接模式。
桥接模式,将抽象部分与他的实现部分分离,使他们都可以独立的变化。
这里解释一下:什么叫抽象与他的实现分离?这并不是说,让抽象类与其派生类分离,因为这没有任何的意义,实现指的是抽象类和他的派生类用来实现自己的对象。
真正的理解的设计原则,很多的设计模式其实就是设计原则的使用,在不知不觉中,我们就使用了很多的设计模式了。
今天的桥接模式我们先讲到这里,下一篇我们讲 命令模式
本系列将持续更新,喜欢的小伙伴可以点一下关注和推荐,谢谢大家的支持
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?