软件工程师,不懂点设计模式怎么行

news2024/11/26 12:41:26

设计模式的圣经

        提起设计模式,就不得不提《设计模式——可复用面向对象软件的基础》这本经典著作。1995年,GOF(Gang Of Four),也就是Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides这四个人,合作出版了《Design Patterns: Elements of Reusable Object-Oriented Software》一书,被奉为设计模式的“圣经”。

                             

         该书描述了23种经典的面向对象设计模式,确立了模式在软件设计中的地位,开创了一种新的面向对象设计思潮。从此,参与设计模式研究的人数呈现爆炸性的增长。

设计模式简介

         设计模式描述了在我们周围不断重复发生的问题,以及该问题的解决方案的核心。这样,你就能一次又一次地使用该解决方案而不必重复劳动。

         设计模式实际上就是类与相互通信的对象之间的组织关系,包括它们的角色、职责、协作方式等各个方面。

         设计模式通常和面向对象编程结合起来使用。面向对象设计模式是“好的面向对象设计”,所谓“好的面向对象设计”是指那些可以满足 “应对变化,提高复用”的设计。

         现代软件设计的特征是:需求频繁变化。设计模式的要点是:寻找变化点,然后在变化点处应用设计模式,从而来更好地应对需求的变化。

软件的复杂性

        软件的复杂性是一个很宽泛的概念,任何使软件难以理解、难以修改、难以维护的东西,都属于软件的复杂性。软件复杂的根本原因是:变化。这里的变化,包括:客户需求的变化、技术平台的变化、开发团队的变化、市场环境的变化等等。

        IBM院士、IBM研究院软件工程首席科学家Grady Booch曾经说过下面这段话,有助于我们理解软件的复杂性和需求变化的随意性。

        “建筑商从来不会去想给一栋已建好的100层高的楼房底下再新修一个小地下室——这样做花费极大而且注定要失败。然而令人惊奇的是,软件系统的用户在要求作出类似改变时却不会仔细考虑,而且他们认为这只是需要简单编程的事。”

解决复杂性

        面向对象设计模式最大的优势在于:抵御变化。为什么能抵御变化呢?原因在于其遵循了以下的设计原则。

        单一职责原则:一个类只负责一个职责,只有一个引起它变化的原因。

        开放封闭原则:对于功能扩展是开放的,对于修改是封闭的。

        里斯替换原则:子类可以替换基类,继承必须确保超类所拥有的性质在子类中仍然成立。

        依赖倒置原则:高层次的模块不应该依赖于低层次的模块,它们都应该依赖于抽象。抽象不应该依赖于具体,具体应该依赖于抽象。

        接口隔离原则:一个类对另一个类的依赖应该建立在最小的接口上。

        迪米特法则:一个对象应当对其他对象有尽可能少的了解,不和陌生人说话。

        封装变化点:使用封装来创建对象之间的分界层,让设计者可以在分界层的一侧进行修改,而不会对另一侧产生不良的影响,从而实现层次间的松耦合。

        优先使用对象组合,而不是类继承:类继承通常为“白箱复用”,对象组合通常为“黑箱复用”。继承在某种程度上破坏了封装性,子类父类耦合度高。而对象组合则只要求被组合的对象具有良好定义的接口,耦合度低。

        下面,我们通过4个具体的案例及其分析过程,来理解设计模式。

案例1

        在面向对象系统的分析与设计过程中,经常会遇到这样一种情况:对于某一个业务逻辑(算法实现),在不同的对象中有不同的细节实现, 但是逻辑(算法)的整体操作结构和框架是稳定的。如何在确保整体操作结构稳定的前提下,来灵活应对不同的细节实现呢?

        解决该问题,主要有以下三种思路:

        1、封装多个不同类,各实现各的(最多把共同的部分拎出来)—— 不使用设计模式。

        2、采用继承和多态的方式实现 —— 模板方法(Template Method)模式。

        3、采用组合和委托的方式实现 —— 策略(Strategy)模式。

        □ 模板方法模式

        定义一个操作中的算法的骨架 (稳定),而将一些步骤延迟(变化)到子类中。 Template Method使得子类可以不改变(复用)一个算法的结构即可重定义(override 重写)该算法的某些特定步骤。

                                      

        模板方法模式使用虚函数的多态性提供了灵活的扩展点,是代码复用方面的基本实现结构。但继承的强制性约束关系也让其有不足的地方,比如:ConcreteClass1中实现的原语方法Primitive1(),是不能被别的类复用的。(可类比:到银行办业务)

        □ 策略模式

        定义一系列算法,把它们一个个封装起来,并且使它们可互相替换(变化)。该模式使得算法可独立于使用它的客户程序(稳定)而变化(扩展,子类化)。

                  

        策略模式将逻辑(算法)封装到一个类(Context)里面,通过组合的方式将具体算法的实现在组合对象中实现,再通过委托的方式将抽象接口的实现委托给组合对象实现。策略模式可以在运行时方便地根据需要在各个算法之间进行切换,同时也解耦了稳定部分和变化部分,变化部分很容易被其他类进行复用。缺点是:多了一个类和一些接口;当变化部分ConcreteStrategy需要使用稳定部分Context中的内部数据和方法时,不太方便。(可类比:旅游出行)

案例2

        在已经封装了一些类和接口的情况下,我们往往会发现,如何使用这些类和接口有时也会成为一个不大不小的问题。原因在于,某些接口之间直接的依赖常常会带来很多问题,甚至根本无法实现。举几个例子:

        1、系统内部存在很多子系统,每个子系统都有各自的类和接口,客户程序使用时,需要调用各个子系统中的接口才能去完成某一项功能。随着客户程序和各子系统的演化,这种过多的耦合面临很多变化的挑战。如何简化客户程序和系统间的交互接口,并将客户程序的演化与内部子系统的变化之间的依赖相互解耦?

        2、由于应用环境的变化,有时候需要将一些现存的类和接口放到新的环境中去使用,但新环境要求的接口是现存接口所不满足的。如何既能利用现有接口的良好实现,同时又能满足新的应用环境?

        3、在面向对象系统中,直接创建某些类的对象会给使用者或系统结构带来很多麻烦。比如:有一个图片对象的类,创建它时,会去磁盘或网络加载图片,如果显示的图片数量非常多,创建的开销是非常大的。如何在不修改已有类和接口的情况下,来管理和控制这些对象特有的复杂性呢?

 

        上述的三个问题,都属于接口隔离的范畴,分别为:

        1、在高层提供统一的接口,内部去调用各子系统的接口 —— 外观(Facade)模式。

        2、采用继承和组合的方式实现一个适配器类,用于适配新老接口 —— 适配器(Adapter)模式。

        3、采用组合和委托的方式实现一个代理类 —— 代理(Proxy)模式。

        □ 外观模式

        为子系统中的一组接口提供一个一致(稳定)的界面。Facade模式定义了一个高级接口,这个接口使得子系统更加容易使用。

                                  

        外观模式不仅简化了整个系统的接口,对于客户程序和内部子系统来说,还达到了一种解耦的效果 —— 内部子系统的任何变化不会影响到Facade接口的变化。(可类比:到政府机构办事)

        □ 适配器模式

        将一个类的接口转换成客户希望的另一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

                            

        适配器模式主要应用于希望复用一些现存的类,但接口又与新的使用环境的要求不一致的情况,在遗留代码复用、类库迁移等方面非常有用。(可类比:笔记本的电源适配器、SD卡读卡器)

        □ 代理模式

        为其他对象提供一种代理以控制对这个对象的访问。

                       

        代理模式通过增加一层间接层的方法,有效解决了面向对象系统中直接使用某些对象带来的问题。这些不好直接使用的对象主要包括:开销大的对象、网络上的对象、需要进行权限控制的对象等。(可类比:房产中介)        

案例3

        常常有一些对象在内部具有特定的数据结构,如果让客户程序依赖这些特定的数据结构,将导致客户程序与这些对象产生极大的耦合。存在以下两种通用情形:

        1、对象内部包含一个容器,客户程序想要访问容器内的信息时,有两种方式:一是直接把容器通过接口返回给客户程序,由客户程序去遍历;二是对象自身提供接口遍历容器内的每个元素。第一种方式耦合性太高,容器结构的变化会导致客户程序的变化。第二种方式虽然能够工作,但不够通用,所有容器对象都需要提供一套类似的接口。如何在不暴露内部数据结构的同时,又能优雅透明地访问其中的元素呢?

        2、对象内部是一个树状结构,如何通过接口去访问对象内的树节点?

        上述两个问题,均与数据结构有关。

        1、在客户程序与聚合对象之间插入一个迭代器,由迭代器专门负责遍历工作,这分离了聚合对象与其遍历行为,对客户也隐藏了其内部细节 —— 迭代器(Iterator)模式。

        2、使用继承的方式实现叶子类和组合类,叶子类和组合类派生自一个公共的基类 —— 组合(Composite)模式。

        □ 迭代器模式

        提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露(稳定)该对象的内部表示。修改遍历方式时,不需要修改聚合对象,只需要修改迭代器即可。

                  

        迭代器模式为遍历不同的集合结构提供了一个统一的接口,从而支持同样的算法在不同的集合结构上进行操作。实际开发时,迭代器可能需要访问聚合对象的内部数据结构,因此聚合对象需要将迭代器设置为友元。(可类比:流水线上的快递包裹)

        □ 组合模式

        将对象组合成树形结构以表示“部分 - 整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性(稳定)。

            

        组合模式采用树形结构将一对多的关系转换为一对一的关系,使得客户程序可以一致地处理叶子对象和组合对象。组合模式去耦合后,客户程序将只依赖于纯粹的抽象接口,从而更能应对变化。(可类比:目录和文件)

案例4

        在软件的开发过程中,随着需求的不断变化和增加,类的个数也会不断变多,最终会导致类的急剧膨胀。

        1、当需要为一个已经定义好的类添加新的职责时,通常情况下我们会定义一个新类继承已定义好的类。添加的职责越多,定义的新类就会越多,最终导致很深层次的继承关系和数不清的类。如果考虑到各种职责的组合,则会导致更多子类的膨胀和重复代码的产生。如何使对象职责的扩展能够根据需要来动态地实现,同时避免带来的子类膨胀问题?

        2、当一个类有一个变化的维度时,我们会派生出两个类。当一个类有两个变化的维度时,我们会派生出四个类。变化的维度越多,派生出的类也会越多。如何利用面向对象技术来使得类型可以轻松地沿着两个乃至多个维度变化,而不引入额外的复杂度?

        如何避免需求变化和增加导致的复杂性呢?可以考虑使用单一职责的设计原则。

        1、通过组合的方式给一个对象添加新的职责,原始的类不需要修改和派生,添加新的装饰类即可 —— 装饰(Decorator)模式。

        2、将系统分为两个独立的部分:抽象部分和实现部分,两个部分可以独立地进行修改,然后通过组合的方式进行桥接 —— 桥接(Bridge)模式。

        □ 装饰模式

        动态(组合)地给一个对象增加一些额外的职责。就增加功能而言,Decorator模式比生成子类(继承)更为灵活(消除重复代码 & 减少子类个数)。

                     

        装饰模式通过组合而非继承的方式,实现了在运行时动态扩展对象职责的能力,而且可以根据需要扩展多个职责,避免了使用继承带来的灵活性差和多子类衍生问题。Decorator类在接口上表现为is-a Component的继承关系,即Decorator类继承了Component类所具有的接口。但在实现上又表现为has-a Component的组合关系,即Decorator类又使用了另外一个Component类。(可类比:杂粮饼)

        □ 桥接模式

        将抽象部分(业务功能)与实现部分(平台实现)分离,使它们都可以独立地变化。

     

        桥接模式使用“对象间的组合关系”解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化。Bridge模式有时候类似于多继承方案,但是多继承方案往往违背单一职责原则,复用性比较差,Bridge模式是比多继承方案更好的解决方法。(可类比:蜡笔和毛笔)

总结

        使用设计模式时,一定要区分需求的稳定部分和可变部分。一个软件必然有稳定部分,这个部分就是核心业务逻辑。如果核心业务逻辑发生变化,软件就没有存在的必要,核心业务逻辑是我们需要固化的。对于可变的部分,需要判断可能发生变化的程度来确定设计策略和设计风险。

         考虑你的设计中什么可能会发生变化,考虑你允许什么发生变化而不让这一变化导致重新设计。设计模式的核心在于发现变化点,并封装之。另外,一种可变性不应散落在代码的很多角落,一种可变性也不应当与另一种可变性混合在一起。

         在实际工作中,很少会规定必须使用哪些设计模式,这样只会带来限制和条条框框。不能为了使用设计模式而去做架构,而是有了做架构的需求后,发现它符合某一类设计模式的结构,再将两者结合。

        设计模式要活学活用,不要生搬硬套。死记硬背是没用的,还要从实践中理解。想要游刃有余地使用设计模式,需要打下牢固的程序设计语言基础,夯实自己的编程思想,积累大量的时间经验,提高开发能力。

         设计模式从来都不是单个设计模式独立使用的。在实际应用中,通常多个设计模式混合使用,你中有我,我中有你。

        □ 软件开发是一项实践工作,最直接的方法就是编程。没有从来不下棋却熟悉定式的围棋高手。掌握设计模式是水到渠成的事情,不要强求。随着理论和实践的不断积累,可能会“渐悟”或者“顿悟”。

         设计模式解决的是设计不足的问题,但同时也要避免设计过度。一定要牢记简洁原则,要知道设计模式是为了使设计简单,而不是更复杂。如果引入设计模式使得设计变得复杂,只能说我们把简单问题复杂化了,问题本身不需要设计模式。

         设计模式的应用不宜先入为主,一上来就使用设计模式是对设计模式的最大误用。没有一步到位的设计模式。敏捷软件开发实践提倡的“Refactoring to Patterns”(重构获得模式),是目前普遍公认的最好的使用设计模式的方法。

         或许有的人会说,我们不需要设计模式,我们的系统很小,设计模式会束缚我们的实现。实际上,设计模式体现的是一种思想,而思想则是指导行为的一切。理解和掌握了设计模式,并不是说记牢了23种或更多的设计场景和解决策略,实际接受的是一种思想的熏陶和洗礼。等这种思想融入到了你的思想中后,你就会不自觉地使用这种思想去进行你的设计和开发,而这才是最重要的。

推荐书籍

            

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/587386.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

感谢你们为科技创新和社会进步做出的贡献

感谢你们为科技创新和社会进步做出的贡献 近日中国科技发展事件 据中国载人航天工程办公室消息,北京时间2023年5月30日6时42分,神舟十六号载人飞行任务航天员乘组出征仪式在酒泉卫星发射中心问天阁圆梦园广场举行。6时44分,中国载人航天工程…

Linux:虚拟网卡技术tun/tap

一、介绍 Linux中的TAP网络接口详解是Linux网络管理中的很重要的一部分,它可以用来建立虚拟网络,模拟网络,管理网络流量以及实现安全网络功能等。本文将介绍TAP网络接口的基本原理、如何使用它进行网络管理、与以太网的区别&#x…

在 I/O 看未来 | Flutter 和 Dart 最新进展

作者 / Google 开发者框架和语言 (含 Flutter、Dart 和 Go) 产品经理/用户体验总监 Tim Sneath 今年的 Google I/O 大会在位于加利福尼亚州山景城的总部附近举办,我们怀着无比激动的心情面向全球直播了这场盛会! 就在三个多月前,我们在肯尼亚…

软考A计划-试题模拟含答案解析-卷十二

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例 👉关于作者 专注于Android/Unity和各种游戏开发技巧,以及各种资源分享&am…

带电接10kV空载电缆线路与架空线路连接引线(绝缘手套作业法)

一、现场复勘 1.核对线路及杆塔号 线路双重名称及杆号无误。 2.检查杆身质量 3.检查电杆埋深 4.检查导线固定情况 检查作业点导线有无烧伤、断股。 5.检查气象条件 作业前需进行湿度和风速的测量,风力大于5级,或湿度大于80%时,不宜带电作…

Microsoft Build 2023 After Party - 上海站

点击蓝字 关注我们 编辑:Alan Wang 排版:Rani Sun 微软 Reactor 为帮助广开发者,技术爱好者,更好的学习 .NET Core, C#, Python,数据科学,机器学习,AI,区块链, IoT 等技术&#xff0…

企业级信息系统开发讲课笔记4.4 Spring Boot加载自定义配置文件

文章目录 零、学习目标一、为什么需要加载自定义配置文件二、使用PropertySource加载自定义配置文件(一)创建Spring Boot项目(二)创建自定义配置文件(三)创建自定义配置类(四)编写测…

一款射频芯片的layout设计指导案例-篇章1

RTL8762C是瑞昱一款超低功耗蓝牙芯片,瑞昱的硬件设计指导书中,关于该芯片的layout设计指导很有普适性的参考指导意义,如下为笔者做过一定简化的芯片最小系统原理图—— PCB Layout建议分如下几个点—— 元件布局顺序 按如下顺序布置元件&…

可视化搭建 - 场景实战

接下来用实战来说明该可视化搭建框架是否好用,以下几条原则需要始终贯穿在下面每个实战场景中: 复杂的业务场景,背后使用的框架 API 是简单的。底层 API 并不为业务场景特殊编写,而是具有很强的抽象性,很容易挖掘出其他…

23种设计模式之状态模式(State Pattern)

前言:大家好,我是小威,24届毕业生,在一家满意的公司实习。本篇文章将23种设计模式中的状态模式,此篇文章为一天学习一个设计模式系列文章,后面会分享其他模式知识。 如果文章有什么需要改进的地方还请大佬不…

Zabbix API开发实战,创建报警媒介和代码示例(付源码)

Zabbix API开始发挥重要作用,尤其是在Zabbix与第三方软件(如配置和事件管理系统)的集成以及日常任务的自动化方面。如果没有一些自动化,管理对数千台主机的监控是非常困难的。 API是在Zabbix 1.8中引入的,并且已经被广…

多语言电商系统_国际化电商系统流程

跨境电商系统是基于计算机技术和互联网平台的一种电子商务系统。它通常包括前端电商网站或应用程序、后台管理系统、物流管理系统、支付系统等多个模块,可以通过网络实现商品展示、订单管理、支付结算、物流配送等电商流程的自动化处理。 跨境电商系统基本流程包括…

JWT | 一分钟掌握JWT | 概念及实例

作者:Mars酱 声明:本文章由Mars酱编写,部分内容来源于网络,如有疑问请联系本人。 转载:欢迎转载,转载前先请联系我! 什么是JWT JWT的全称是Json Web Token。是基于RFC 7519开放标准的&#xff…

玩转ChatGPT:视频制作

一、写在前面 最近,在码深度学习图像识别的相关知识和代码,这一part,看看能否用小Chat搞一个介绍视频。 简单问小Chat: 咒语:我怎么使用你做一个视频?需要配合什么软件生成?? 大意…

2023.5.22-5.28 AI行业周刊(第149期):毕业10年后的实验室聚会

周末和实验室,无锡这边师兄弟们相聚了一次,之前在无锡这边的江南大学读书,后来工作后大家大多数也都留在了无锡。 我们研究生时的实验室,专门有一个微信群,从02年入学,到17年入学,多年各界的师…

Node.JS学习 | Babel | webpack | ES6

💗wei_shuo的个人主页 💫wei_shuo的学习社区 🌐Hello World ! Node.JS Node.JS能够在服务器端运行JavaScript的开放源代码、跨平台运行环境;Node.js采用Google开发的V8运行代码,使用事件驱动、非阻塞IO和异…

Python常用数据结构

Python 提供了多种内置的数据结构,用于存储和组织数据。以下是一些常见的 Python 数据结构: 1.列表(List):列表是一个有序、可变的数据集合,可以包含任意类型的元素。列表使用方括号 [] 表示,元…

谷歌周彦祺:LLM浪潮中的女性科学家多面手丨智源大会嘉宾风采

导读 大模型研发竞赛如火如荼,谷歌紧随OpenAI其后推出PalM2、Gemini等系列模型。Scaling Law是否仍然适用于当下的大模型发展?科技巨头与初创企业在竞争中各有哪些优势和劣势?模型研究者应秉持哪些社会责任? 2023智源大会「基础模…

华为OD机试真题B卷 Java 实现【最长的连续子序列】,附详细解题思路

一、题目描述 有N个正整数组成的一个序列,给定一个整数sum,求长度最长的的连续子序列使他们的和等于sum,返回该子序列的长度,如果没有满足要求的序列返回-1。 二、输入描述 第1行有N个正整数组成的一个序列。 第2行给定一个整数sum。 求最长连续子序列,只要遍历计算连…

补贴平价好书影响上亿读者:有一种力量叫“至拙”

通过一种“至拙”的力量,“多多读书月”带来诸多可喜的变化。 全民拼书:“多多读书月”带来平价好书 如果你也是一位热爱读书的小伙伴,那么想来一定不会不知道“多多读书月”。 2020年,在“三区三州”地区助力脱贫攻坚的公益活动中…