当前位置: 首页 > news >正文

【设计模式】桥接模式

系列文章目录


文章目录

  • 系列文章目录
    • 紧耦合的程序演化
    • 合成/聚合复用原则
    • 松耦合的程序
    • 桥接模式
    • 桥接模式基本代码
  • 总结


在这里插入图片描述

2007年苹果手机尚未出世,手机操作系统多种多样(黑莓、塞班、Tizen等),互相封闭,而如今手机操作系统只剩下苹果OS和安卓,鸿蒙正在稳步进场,本章内容也将在2007年那个特定背景故事下展开。

我们知道在计算机领域里,有了Windows操作系统,使得所有的PC厂商不用关注软件,而软件制造商也不用过多关注硬件,这对计算机的整体发展是非常有利的,而有个别品牌的电脑公司自己开发操作系统和应用软件,尽管充满了创意,但是却因为不能和其他软件整合,导致发展缓慢。手机也是这种情况,操作系统多种多样,软件并不能完全整合。

紧耦合的程序演化

但其实这里蕴含两种完全不同的思维方式,分别是手机硬件软件和PC硬件软件

如果现在有一个N品牌的手机,它有一个游戏,我要玩游戏程序应该怎么写

//手机品牌N的游戏class HandsetBrandGame{public void run(){System.out.println("运行N品牌手机游戏");}}

客户端代码:

 public static void main(String[] args) {HandsetBrandGame game = new HandsetBrandGame();game.run();}

如果现在又有一个M品牌的手机,也有小游戏,客户端也可以调用。这里是两个品牌都有游戏,从面向对象的思想来说,应该有一个父类‘手机品牌游戏’,然后让N和M品牌的手机游戏都继承于它,这样可以实现同样的运行方法。

 //手机游戏类class HandsetGame{public void run(){}}

M品牌游戏和N品牌游戏

 //手机品牌M的游戏class HandsetBrandMGame extends HandsetGame{public void run(){System.out.println("运行M品牌手机游戏");}}//手机品牌N的游戏class HandsetBrandNGame extends HandsetGame{public void run(){System.out.println("运行N品牌的手机游戏");}}

另外,由于手机都需要通讯录功能,于是N品牌和M品牌都增加了通讯录的增删改查功能,这样的话就有些麻烦了,那就意味着,父类应该是手机品牌,下有‘手机品牌M’和‘手机品牌N’,每个子类下各有‘通讯录’和‘游戏子类’。

  class HandsetBrand{public void run(){}}//手机品牌Mclass HandsetBrandM extends HandsetBrand{}//手机品牌Nclass HandsetBrandN extends HandsetBrand{}//下属的各自通讯录类和游戏类class HandsetBrandMGame extends HandsetBrandM{public void run(){System.out.println("运行M品牌的游戏");}}class HandsetBrandNGame extends HandsetBrandN{public void run(){System.out.println("运行N品牌的游戏");}}class HandsetBrandMAddressList extends HandsetBrandM{public void run(){System.out.println("运行M品牌手机通讯录");}}class HandsetBrandNAddressList extends HandsetBrandN{public void rum(){System.out.println("运行N品牌手机通讯录");}}//客户端public static void main(String[] args) {HandsetBrand ab;ab =new HandsetBrandMAddressList();ab.run();ab = new HandsetBrandMGame();ab.run();ab = new HandsetBrandNAddressList();ab.run();ab = new HandsetBrandNGame();ab.run();}

这个结构是可以的,如果现在需要每个品牌都增加一个音乐播放器功能,只需要在每个品牌的下面都增加一个子类,这几个子类差别是不大,代码类似,但是因为品牌不同,增加功能只能这样。现在又来了一家新的手机品牌‘S’,他也有游戏、通讯录、音乐播放器,我们照上述代码功能,就只能增加手机品牌S类和其三个下属功能子类。这样似乎越来越麻烦了。

我们似乎是一直在用面向对象的理论设计的,先有品牌,然后多个品牌就抽象出一个品牌抽象类,对于每个功能,就都继承其各自的品牌。但是还是造成越来越麻烦的地步,这是因为在某些情况下,使用继承会带来麻烦。比如,对象的继承关系实在编译时就定义好了,所以无法在运行时改变从父类继承的实现。子类的实现与它的父类有非常紧密的依赖关系,以至于父类实现中的任何变化必然会导致子类发生变化。当你需要复用子类时,如果继承下来的实现不适合解决新的问题,则父类必须重写或被其他更适合的类替换。这种依赖关系限制了灵活性并最终限制了复用性。

这样的继承结构,如果不断增加品牌或新功能,类会越来越多的
在面向对象设计中,我们还有一个很重要的设计原则,那就是–合成/聚合复用原则 ,即优先使用对象合成/聚合,而不是类继承。

合成/聚合复用原则

合成/聚合复用原则,尽量使用合成/聚合,尽量不要使用类继承

合成和聚合都是关联的特殊种类。聚合表示一种弱的“拥有”关系,体现的是A对象可以包含B对象,但B对象不是A对象的一部分;合成则是一种强的“拥有”关系,体现了严格的部分和整体的关系部分和整体的生命周期一样。比方说,大雁有两个翅膀,翅膀和大雁就是部分和整体的关系,并且它们的生命周期是一样的,于是大雁和翅膀就是合成关系。而大雁是群居动物,所以每只大雁都是属于一个雁群,一个雁群可以有多只大雁,所以大雁和雁群是聚合关系。

合成/聚合复用原则的好处是,优先使用对象的合成/聚成将有助于你保持每个类被封装,并被集中在单个任务上。这样类和类继承层次会保持较小规模,并且不太可能增长为不可控制的庞然大物。 就刚才的例子,我们要学会用对象的职责,而不是结构来考虑问题。其实答案就在我们聊到的手机和电脑的差别上。

手机是不同的品牌公司,各自做自己的软件,就像我们现在的设计一样,而PC却是硬件厂商做硬件,软件厂商做软件,组合起来才可以用的机器。

实际上,像游戏、通讯录、音乐播放这些功能都是软件,如果我们可以让其分离与手机的耦合,那么就可以大大减少面对新需求时改动多大的不合理情况。所以就是该有个手机品牌抽象类和手机软件抽象类,让不同的品牌和功能都分别继承于他们,这样要增加新的品牌或新的功能就不用影响其他类了。 手机品牌和手机软件之间关系是什么呢?我们知道,手机品牌包含手机软件,但是软件并不是品牌的一部分,所以他们之间是聚合关系。

松耦合的程序

手机软件抽象类:

 //手机软件abstract class HandsetSoft{//运行public abstract void run();}

游戏、通讯录等具体类:

 //手机游戏class HandsetGame extends HandsetSoft{@Overridepublic void run() {System.out.println("手机游戏");}}//手机通讯录class HandsetAddressList extends HandsetSoft{@Overridepublic void run() {System.out.println("通讯录");}}

手机品牌类:

   abstract class HandsetBrand{protected HandsetSoft soft;//设置手机软件public void setHandsetSoft(HandsetSoft soft){this.soft = soft;}//运行public abstract void  run();}

品牌M和品牌N具体类:

//手机品牌Mclass HandsetBrandM extends HandsetBrand{@Overridepublic void run() {System.out.println("品牌M");soft.run();}}//手机品牌Nclass HandsetBrandN extends HandsetBrand{@Overridepublic void run() {System.out.println("手机品牌N");soft.run();}}

客户端代码:

public static void main(String[] args) {HandsetBrand ab;ab = new HandsetBrandM();ab.run();ab.setHandsetSoft(new HandsetGame());ab.run();ab.setHandsetSoft(new HandsetAddressList());ab.run();HandsetBrand ab2;ab2 = new HandsetBrandN();ab2.setHandsetSoft(new HandsetGame());ab2.run();ab2.setHandsetSoft(new HandsetAddressList());ab2.run();}

这样的代码感觉就会好很多,要是需要增加一个功能,比如音乐播放功能,那么只需要增加这个类就可以了。不会影响其他任何类。类的个数增加也只是一个

 class HandsetMusicPlay extends HandsetSoft{@Overridepublic void run() {System.out.println("音乐播放");}}

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

 class HandsetBrandS extends HandsetBrand{@Overridepublic void run() {System.out.println("品牌S");soft.run();}}

这也符合我们面向对象设计中的开放-封闭原则,但是代码主要还是凸显的合成/聚合复用原则,也就是优先使用对象的合成或聚合,而不是类继承。 盲目的继承会带来麻烦,主要是因为继承是一种强耦合结构,父类变,子类也就必须变, 所以在用继承时,一定要在是“is-a”的关系时再考虑使用,而不是任何时候再去使用。这也就是我们的设计模式–桥接模式。

桥接模式

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

这里我们需要理解下,什么叫抽象与它的实现分离,这并不是说,让抽象类与其派生类分离,因为这没有任何意义,实现指的是抽象类和他的派生类用来实现自己的对象。 就刚才的例子而言,就是让‘手机’即可以按照品牌来分类,也可以按照功能来分类。
按品牌分类实现结构图:
在这里插入图片描述
按软件实现结构图:
在这里插入图片描述

由于实现方式多种,桥接模式的核心意图是把这些实现独立出来,让他们各自变化。这就使得每种实现的变化不会影响其他实现,从而达到应对变化的目的。
在这里插入图片描述

桥接模式基本代码

桥接模式结构图:

在这里插入图片描述
桥接模式代码基本如下:

abstract class Implementor{public abstract void operation();}class ConcreteImplementorA extends Implementor{@Overridepublic void operation() {System.out.println("具体实现A的方法执行");}}class ConcreteImplementorB extends Implementor{@Overridepublic void operation() {System.out.println("具体实现B的方法执行");}}abstract class Abstraction{protected  Implementor implementor;public void setImplementor(Implementor implementor){this.implementor = implementor;}public abstract void operation();}class RefinedAbstraction extends Abstraction{@Overridepublic void operation() {System.out.println("具体的Abstraction");implementor.operation();}}public static void main(String[] args) {Abstraction ab;ab = new RefinedAbstraction();ab.setImplementor(new ConcreteImplementorA());ab.operation();ab.setImplementor(new ConcreteImplementorB());ab.operation();}

桥接模式所说的‘将抽象部分与它的实现分离’,其实就是实现系统可能有多角度分类,每一种分类都与有可能变化,那么就把这种多角度分离出来让他们独立变化,减少他们的耦合。 也就是说,当我们需要多角度去分类实现对象,而只用继承会造成大量的类增加,不能满足开放-封闭原则,这时就需要考虑用桥接模式了。

总结

以上就是本文全部内容,本文主要向大家介绍了设计模式中的桥接模式,通过手机软件与手机系统不匹配实例开篇,之后介绍合成/聚合复用原则,最后引出桥接模式基本代码,并改造案例代码为桥接模式。感谢各位能够看到最后,如有问题,欢迎各位大佬在评论区指正,希望大家可以有所收获!创作不易,希望大家多多支持!


文章转载自:

http://IRjHqOEv.dzfwb.cn
http://abXGzzW3.dzfwb.cn
http://HvAzDIxk.dzfwb.cn
http://O73Sf80S.dzfwb.cn
http://7fMLoT5R.dzfwb.cn
http://Dsxfe3YJ.dzfwb.cn
http://TVk7Gli4.dzfwb.cn
http://PuwjPwxD.dzfwb.cn
http://eX40Pd6b.dzfwb.cn
http://lyIkKtGh.dzfwb.cn
http://p6P1rP0v.dzfwb.cn
http://J6UkZVgu.dzfwb.cn
http://WAMTaClW.dzfwb.cn
http://ta2YR5EU.dzfwb.cn
http://O19Sse9H.dzfwb.cn
http://Ir9MArQa.dzfwb.cn
http://N1ldt63O.dzfwb.cn
http://EBLOJ64g.dzfwb.cn
http://hwzrF6yL.dzfwb.cn
http://HI1bFLqQ.dzfwb.cn
http://G8vhLDoI.dzfwb.cn
http://cbrcMN8V.dzfwb.cn
http://HZIqLitE.dzfwb.cn
http://MPrJWKK3.dzfwb.cn
http://YsRugLc4.dzfwb.cn
http://ceyy7bW6.dzfwb.cn
http://oqrB9WuB.dzfwb.cn
http://yoJxyQl7.dzfwb.cn
http://zGySLkyG.dzfwb.cn
http://Vh7b79DX.dzfwb.cn
http://www.dtcms.com/a/387510.html

相关文章:

  • ACP(五):优化提示词(Prompt),精细地控制大模型的输出
  • Egg.js 性能测试全解析:从压力测试到深度调优
  • 自制脚本,解决Ubuntu20.04 键盘会突然失灵、键盘延迟突然很大问题
  • 172.在vue3中使用openlayers:引用 hover 效果,展示各种鼠标 cursor 样式
  • SpringBoot Oracle
  • LLMs之IR:《MUVERA: Multi-Vector Retrieval via Fixed Dimensional Encodings》的翻译与解读
  • Redis与Java集成实战:从入门到高级应用
  • Chromium 138 编译指南 macOS篇:配置depot_tools(三)
  • qt QHXYModelMapper详解
  • 机器学习中的编码问题和标准化:类别编码、one-hot编码缺陷及改进
  • Qt QHorizontalStackedBarSeries详解
  • Python爬虫实战:研究Pandas,构建全运会数据采集和分析系统
  • 告别冗余 DOM:Vue Fragment 用法与性能优化解析
  • 快速排序:原理、实现与优化
  • JavaScript性能优化实战:深入剖析性能瓶颈与最佳实践
  • Lattice ECP5系列FPGA介绍
  • PySpark 窗口函数row_number、lag、lead的使用简述
  • 华为FreeBuds 7i不同设备要如何连接?
  • 使用LVS架设服务器集群系统实现负载均衡与高可用的知识点详解
  • 84-dify案例分享-使用Qwen-Image实现文生图、图生图
  • 留个档,Unity,Animation控制相机,出现抖动的问题记录
  • CentOS 8.5部署Zabbix6.0 server端
  • CentOS系统下安装Docker记录
  • CentOS 7 如何安装 EPEL 源?epel-release-latest-7.noarch.rpm 安装教程(附安装保存)
  • CentOS 7 源码版 PhpMyAdmin 安装指南(适配 Nginx+PHP-FPM 环境)
  • 在 WSL Ubuntu 上使用 Docker 搭建可被 Ansible 控制的受控节点环境
  • 数据赋能,安全护航——D-QS工程造价数字化平台的数据治理与行业应用
  • Matplotlib 可视化:从基础绘图到高级定制
  • 知识管理与高效学习技术
  • 【AI总结】万字长文预警!Spring Boot 4 全景深度解析:从虚拟线程到声明式 HTTP 客户端,再到云原生最佳实践