一、设计模式是什么?
设计模式是为了解决在软件开发过程中遇到的某些问题而形成的思想。同一场景有多种设计模式可以应用,不同的模式有各自的优缺点,开发者可以基于自身需求选择合适的设计模式,去解决相应的工程难题。
良好的软件设计和架构,可以让代码具备良好的可读性、可维护性、可扩展性、可复用性,让整个系统具备较强的鲁棒性和性能,减少屎山代码出现的概率。
想要熟练运用设计模式,提高自己的编程能力和架构能力,只有在自己工作中,结合自身工作内容,多思考多实践。本文只能通过举一些通俗的例子,来帮助你更快或者说更容易地去理解设计模式的一些底层逻辑,光看可不行哦,一定要多动手实操,看完本文就去实践吧。
二、六大设计原则
2.1 单一职责原则
该原则:对一个类而言,其职责应该只有一个,即只有一个可以引起其改变的原因。
若类承担的职责过多,其内部耦合性就大大增加,某个职责的变化会带给其他职责不可预估的影响,这样设计会使得类相对脆弱。
通俗地理解就是,你如果在当保安,就不要老想着帮清洁打扫卫生,你的领导可能会因为你打扫卫生夸你,但更可能会因为你保安工作没做到位而骂你。
2.2 开闭原则
该原则:面向对象编程领域中规定,软件中的对象应该对于扩展是开放的,对于修改是封闭的,即一个实体允许在不改变它原有源代码前提下变更其行为。
开闭原则可以使我们的软件具备较优的鲁棒性,代码更模块化且利于扩展和维护,从而提高开发效率。经常开发的工程师都清楚,在一个旧的系统中添加新的功能是很不容易的,如果你添加功能要大量修改原有的代码内容,大概可能出现如下几类情况:
- 你能力特强,对原有系统非常了解,也可能本来就是你写的,你添加的功能不仅完成了业务需求,还没有给系统带来bug和崩溃。无非就是测试工程师可能要重新进行一整轮的整体测试工作,然后不太开心,也还好啦。
- 你修改或添加的内容让系统跑不起来了,那你就要重写你的代码或者研究如何修复你产生的bug,当然也可能会埋下未知的雷。大概率测试工程师会天天问候你。工作效率大大降低,这也是大多数人面临的情况,懂得都懂。
- 你修改或添加的内容完成了业务需求,也没有让系统崩溃,但你也不清楚会不会引起其他地方在某些情况下出现未知bug,测试工程师大概率也没测出来,毕竟常规情况下不会异常,但未来可能会爆雷,那就让未来的人自求多福吧。这类情况,即使你的代码没有问题,但会让系统更加复杂,更加不可读,更加无法扩展。
综上来看,如果系统有良好的设计,也尽可能满足了开闭原则,那么后续在扩展新功能时,原有系统不会受到影响,只需要针对新扩展的部分重点测试即可(测试工程师狂喜),即便是扩展的功能出现了问题,也相对容易进行修复,最坏就是把扩展的内容全删掉罢了。
2.3 里氏代换原则
该原则:任何基类可以出现的地方,子类一定可以出现。
该原则是继承复用的基石,只有子类能完全替换基类,且功能不受影响,基类才算是真正被复用。想满足该原则,一般来说就尽量从抽象类继承,而不是从具体类继承,还可以根据如下4点来判断:
- 子类必须实现父类的抽象方法,但不能重写父类的非抽象方法(已实现)。你重写了那就没法替换了。
- 子类可以增加自己特有的方法。父类有的子类也有就行。
- 子类实现父类的方法,输入要比父类的输入更宽松。比如父类输入一个hashmap,子类就输入一个map,map是包含hashmap的,这样无论用子类父类调用该方法,只要输入hashmap,都用的父类方法。
- 子类实现父类的方法,输出要比父类的输出更严格。比如父类输出一个map,子类就输出一个hashmap,这样只要输出map,都用的父类方法,反过来编译器会报错。
里氏代换原则可以很大程度降低代码出问题的概率,也是一个良好的设计和编程习惯。
2.4 依赖倒置原则
该原则:程序要依赖于抽象接口,不要依赖于具体实现,对抽象编程,不要对实现编程,这样降低了客户端和实现模块间的耦合。一般情况抽象变化的概率小,依赖于抽象,即使实现细节一直变化,也不影响客户端。
想遵循该原则,尽量满足如下4点要求:
- 每个类尽量有接口或抽象类,这样才能基于抽象编程。
- 变量的类型尽量是接口或抽象类。
- 尽量不要从具体类继承。
- 尽量不要覆盖基类或父类已实现的方法。
依赖倒置原则的目的就是实现解耦,高层模块不再依赖于具体实现。
2.5 最小知识原则
该原则:也称为迪米特原则,如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果需要调用另一个对象的某个方法,可以通过引入一个合理的第三者来调用,从而降低他们之间的耦合度。
想遵循该原则,尽量满足如下3点要求:
- 尽可能创建松耦合的类,这样复用率高,当发生修改时,相关联的类也不会有太大的影响。
- 类的结构设计中,成员变量和成员方法的访问权限尽可能降低。
- 尽可能把类设计为不变类。
最小知识原则尽可能地减少了对象间的联系,保持功能模块相对独立,降低依赖。
2.6 合成复用原则
该原则:在一个新的对象中使用已有的对象,使之成为新对象的一部分,通过委派调用已有对象的方法达到复用目的,应优先采用合成/聚合的方式,其次采用继承方式。
通过合成/聚合复用的优势:
- 维持了封装性。看不到已有对象的内部细节,属于黑箱复用。
- 耦合度低。所需依赖少,新对象只能通过成分对象的接口调用其方法。
- 复用的灵活性高。运行时动态进行复用,可引用与成分对象类型相同的对象。
应用合成复用原则,不要放松对嵌入对象的管理,尤其是嵌入对象过多时,这很考验开发者的实力。
三、23种软件设计模式
3.1-3.5是创建型模式,3.6-3.12是结构型模式,3.13-3.23是行为型模式。
3.1 单例模式
单例模式是一种创建型的软件设计模式,在工程项目中非常常见。通过单例模式的设计,使得创建的类在当前进程中只有一个实例,并提供一个全局性的访问点,这样可以规避因频繁创建对象而导致的内存飙升情况。
实现单例模式的三个要点:
1)私有化构造函数:这样外界就无法自由地创建类对象,进而阻止了多个实例的产生。
2)类定义中含有该类的唯一静态私有对象:静态变量存放在全局存储区,且是唯一的,供所有对象使用。
3)用公有的静态函数来获取该实例:提供了访问接口。
单例模式一般分为懒汉式和饿汉式。
1)懒汉式:在使用类对象(单例实例)时才会去创建它,不然就懒得去搞。
2)饿汉式:单例实例在类装载时构建,有可能全局都没使用过,但它占用了空间,就像等着发救济粮的饿汉提前排好队等吃的一样。
实现单例模式通常面临两个问题,线程安全和内存泄漏。针对线程安全,可通过双重检测锁的方式来解决;针对内存泄漏,可通过资源管理的方式来解决,如智能指针和静态嵌套类。
具体代码实现详情见:
3.2 工厂模式
工厂模式是一种创建型的软件设计模式。定义一个用于创建对象的工厂接口,并让工厂子类决定实例化哪一个产品类,使产品类的实例化延迟到工厂子类中执行。说白了就是用来造东西的,一般是比较简单的东西,我们不需要知道它如何生产的,直接从工厂拿到产品即可。
工厂模式的优点:
- 良好的封装性。将产品的实例化封装执行,避免被修改,这样的产品具备良好的一致性。
- 良好的扩展性。增加产品时,同步增加一个工厂子类,不会违反开闭原则。
- 标准的解耦合框架。使用者只需要知道自己要什么产品即可,不用去管产品具体的特性等等,降低了模块间的耦合。
工厂模式的缺点:
- 代码量大。每加一个产品,都要加一个工厂子类,代码会显得臃肿。
- 不利于扩展复杂的产品结构。如果你要苹果、香蕉、梨,工厂模式的结构还可以,但如果你要山东的苹果、海南的香蕉、北京的苹果,就显得结构呆呆的。这可以用抽象工厂模式解决,对产品族和产品种类进行区分。
举例:水果工厂生产,具体代码实现详情见:
3.3 抽象工厂模式
抽象工厂模式是一种创建型的软件设计模式,该模式相当于升级版的工厂模式。
如果说工厂模式对应一个产品系列,那抽象工厂就对应了多个产品系列。比如工厂模式中有鞋子、衣服和裤子可以生产,那抽象工厂模式就会衍生出耐克工厂和阿迪工厂,这两个工厂分别生产各自品牌的鞋子、衣服和裤子,客户只需要选择具体工厂和想要的产品即可。如果想要替换产品系列,也只需要将具体工厂切换为别的品牌就行了。
抽象工厂模式的优点:
- 具体类分离。具体产品类在具体工厂的实现中进行了分离和归类。
- 易于更换产品族。当客户想要改变整个产品族时,只需要切换具体工厂即可。
- 利于产品一致性。当产品族的各个产品需要在一起执行时,抽象工厂可以确保客户只操作同系列产品,而不会进行跨品牌的组合。
抽象工厂模式的缺点:
- 不利于添加新种类产品。每加一个新的种类,如多一个项链类型的产品,那每一个具体工厂都要进行代码的扩展,且破坏了原先规定的结构,违反了开闭原则。
举例:不同国家水果工厂生产,具体代码实现详情见:
3.4 建造者模式
建造者模式是一种创建型的软件设计模式,用于构造相对复杂的对象。
建造者模式可以将复杂对象的构建与它的表示分离,使得相同的构建过程可以得到不同的表示。如果说工厂模式和抽象工厂模式更注重产品整体,那建造者模式则更在乎产品的组成和细节。
建造者模式的优点:
- 封装性好。有效地封装了建造过程(主要业务逻辑),使得系统整体的稳定性得到了一定保证。
- 解耦。产品本身和建造过程解耦,相同的建造过程可以创建出不同的产品。
- 产品建造过程精细化。该模式注重产品创建的整个过程,将复杂的步骤拆解得到多个相对简单的步骤,使得系统流程更清晰,且对细节的把控更精准。
- 易于扩展。如果有新产品需求,只需要添加一个建造者类即可,不需要改动之前的代码,符合开闭原则。
建造者模式的缺点:
- 产品的组成部分和构建过程要一致,限制了产品的多样性。
- 若产品内部有结构上的变化,则整个系统都要进行大改,增加了后期维护成本。
3.5 原型模式
原型模式是一种创建型的软件设计模式,通俗的来讲就是复制粘贴。
通过一个原型对象,快速地创建出多个一致的对象,并对其进行相关的操作。比如文件夹中存放了一个Word文件,你把文件复制了一个副本出来,原件不动,对副本进行修改以达到自己的目的。原型像是一个模板,你可以基于它复制好多对象,而复制出来的副本产生任何变化都不会影响到原型(注意:前提是clone的实现要满足深拷贝)。
原型模式的优点:
- 便捷、简洁、高效。不需要考虑对象的复杂程度,只需要复制即可。
- 无需初始化。可动态地获取当前原型的状态,并在当前基础上进行拷贝。
- 允许动态增加或减少产品类。
原型模式的缺点:
- 每个类都需要配备一个clone函数,若对已有的类进行改造,需要修改其源码,违背了开闭原则。
举例:文件复制粘贴,具体代码实现详情见:
3.6 适配器模式
适配器模式是一种结构型的软件设计模式,也称包装模式,即将相对复杂的功能(可能用到多个类)封装起来,提供一个使用者想要的接口,使用者只需要调用接口,不需要知道接口里封装的内容是如何实现的。
个人工作中经常能用到适配器模式,比如在面对一些第三方库或者SDK开发时,它们的接口往往与我们自己想要的接口不一致,此时适配器模式可以很好地扮演一个接口转换器的角色,将别人的接口与我们的接口对应上。
适配器模式的优点:
- 良好封装性。接口内的内容对使用者而言是透明的,即看不见,这确保了内部功能具备较好的封装性,不易被改动。
- 解耦。不匹配的两方在适配器的作用下可以做到解耦,不需要修改任何一方原有代码逻辑。
- 良好复用性。适配的两方不需要做任何修改,业务的实现可以通过适配器来完成,不同的业务可以使用不同的适配器。
- 良好扩展性。若要增加业务场景,只需要增加适配器类,来满足业务即可。
适配器模式的缺点:
- 不利于维护。因为业务的实现基于适配器完成,适配器中代码的复杂程度会越来越高,不熟悉业务或者底层逻辑的人难以短时间内接手维护。
- 系统结构易混乱。当业务量快速增加时,适配器类的数量也会快速增加,没有良好的系统架构布局,最终会使得整个系统臃肿且危险。
举例:调用第三方相机SDK,具体代码实现详情见:
类适配器模式:
对象适配器模式:
3.7 桥接模式
桥接模式是一种结构型的软件设计模式,将抽象部分与实现部分分离,使他们可以独立地变化。
举例来说,黑色钢笔、红色油笔、红色钢笔等等,如果颜色和笔类型合起来考虑,那类的复杂度将难以想象,若有10个颜色,10个笔类型,那要有10*10个类来涵盖所有类型的笔。但是如果拆分颜色和笔类型,通过组合的形式获得目标,那只要10+10个类即可。这样设计极大降低了系统复杂度和耦合程度。
桥接模式的优点:
- 扩展性好。抽象与实现分离,扩展起来更便捷,可以获得更多样式的目标。
- 解耦。不同抽象间的耦合程度低。
- 满足设计模式要求的合成复用原则和开闭原则。
- 封装性好。具体实现细节对客户而言是透明不可见的。
桥接模式的缺点:
- 使用场景有限制。只有系统有两个以上独立变化维度时才适用。
举例:画笔绘画,具体代码实现详情见:
3.8 组合模式
组合模式是一种结构型的软件设计模式,将对象组合成树形结构,以凸显“部分-整体”的层次结构,使客户端对单个对象和组合对象的操作具备一致性。
组合模式和桥接模式都应用了组合的思想,不同之处在于:桥接模式侧重于同级别间的组合,如多个属性的组合,避免了类爆炸;组合模式侧重于部分和整体的组合,避免了单对象和组合对象的区别对待,那样会增加程序复杂度。
组合模式的优点:
- 层次鲜明。凸显“部分-整体”的层次结构。
- 一致性。对叶子对象(单)和容器对象(组合)的操作具备良好一致性。
- 节点自由度高。在结构中按需自由添加节点。
组合模式的缺点:
- 设计更抽象。
- 应用场景限制。
举例:文件系统,具体代码实现详情见:
透明式组合模式:
安全式组合模式:
3.9 装饰器模式
装饰器模式是一种结构型的软件设计模式,在不改变原类文件或使用继承的前提下,动态地扩展一个对象,进而达到增强或者增加对象功能的目的。
装饰器模式的优点:
- 灵活性好。相比较继承,装饰模式扩展对象功能更加灵活。
- 扩展性好。不同装饰组合,可以创造出各式各样的对象,且避免了类爆炸。
- 满足设计模式要求的开闭原则和合成复用原则。
- 透明性好。客户端针对抽象操作,对具体实现的内容不可见。
装饰器模式的缺点:
1.复杂性高。装饰模式的设计往往具备较高复杂度,对开发者的水平要求高。
举例:炒菜加料,具体代码实现详情见:
3.10 门面模式
门面模式是一种结构型的软件设计模式,也叫外观模式,它提供了统一的接口去访问多个子系统的接口。举个例子,一个餐馆里有许多角色,每个角色就是一个子系统,餐馆就是总系统,客人来餐馆只需要按要求点餐,不需要管餐馆是怎么运作的。
门面模式的优点:
- 简洁易使用。为复杂的模块和系统提供了一个简单的接口,简易化操作。
- 保证了子系统独立性。子系统间独立性良好,彼此间一般不受影响,如何使用由门面决定。
- 保证了系统稳定性。当直接使用子系统,可能会出现无法预知的异常时,门面模式可通过高层接口规范子系统接口的调用,且有效阻隔子系统和客户端间的交互,进而增强系统鲁棒性。
- 隐秘性好。门面将子系统的具体细节都封装了起来。
门面模式的缺点:
- 不符合开闭原则。添加新系统要对门面进行修改。
- 对开发者要求高。开发者需要了解子系统间的业务逻辑关系,这样才能确保封装的高层接口是有效且稳定的。
举例:餐馆吃饭,具体代码实现详情见:
3.11 代理模式
代理模式是一种结构型的软件设计模式,在不改变原代码前提下,提供一个代理,以控制对原对象的访问。
代理模式的优点:
- 职责清晰。真实对象专注于自身业务逻辑,不用考虑其他非本职内容,交给代理完成。
- 高拓展性。真实对象的改变不影响代理。
- 解耦。将客户端与真实对象分离,降低系统耦合度。
- 提高性能。虚拟代理可以减少系统资源的消耗。
- 高安全性和稳定性。代理能很好地控制访问,提高程序安全。
代理模式的缺点:
- 增加系统复杂度。代理的职责往往较冗杂。
- 请求速度降低。客户端与真实对象中加入代理,一定程度上会降低整个系统流程的运行效率。
举例:游戏代理,具体代码实现详情见:
3.12 享元模式
享元模式是一种结构型的软件设计模式,通过共享对象的方式,尽可能减少内存占用,从而达到优化的目的。
就像打麻将,同时有10桌在玩,每桌都有4个"八筒",如果建立40个"八筒"对象,那就非常冗余,但如果用享元模式建立一套麻将牌,每桌打出"八筒"时,就调用享元中的"八筒",相当于只用了1个对象,这样即节省了资源,也完成了需求。
上述例子中,桌号和牌号就是享元模式的外蕴状态,如A1八筒,就是A桌的第一个"八筒",A和1是外蕴状态;而卡牌"八筒"本身就是内蕴状态,内蕴是可以共享的。外蕴随环境变化,占用资源也少的多,往往只是简单的数据结构。
享元模式的优点:
- 减少资源浪费。共享资源极大程度降低了系统的资源消耗。
- 提高系统运行效率。当资源过度使用时,系统效率会大受影响。
享元模式的缺点:
- 维护共享对象,需要额外开销。
- 系统复杂度提高。运行享元,除了内外状态,还有线程方面都要充分考虑。
举例:模拟打牌,具体代码实现详情见:
3.13 策略模式
策略模式是一种行为型的软件设计模式,针对某个行为,在不同的应用场景下,有不同的实现算法,并且可以互相替换。比如两军交战,军队会采用不同的阵法、策略、兵法应对不同的战况。
策略模式的优点:
- 便于管理算法族。一套策略对应一套算法,该模式可以很好地进行维护和管理。
- 避免使用多重条件语句,如if else和switch。庞大的系统中,过多的多重条件语句会让代码显得臃肿和不易维护,提高出错概率。
- 封装算法。提高了算法的保密性和安全性。
- 符合开闭原则。
策略模式的缺点:
- 客户端需要了解每种策略,并自行决定何时何地使用何种策略。
- 随着策略增多,策略类数量增加,要注意对类的维护。
举例:军队策略,具体代码实现详情见:
3.14 模板模式
模板模式是一种行为型的软件设计模式,在父类中定义了一个模板算法,只实现模板中的公共部分,将可变部分放在子类中实现,不同的子类对同一模板有不同的扩展和实现。
模板模式的优点:
- 良好复用性。父类中公共部分可以多次使用,具备好的环境适应性。
- 良好扩展性。子类对父类模板的具体实现作扩展。
- 符合开闭原则。基于模板扩展功能,不需要改动原有代码。
模板模式的缺点:
- 类个数增加。基于模板的每个实现,都要定义一个子类,容易使代码量膨胀。
- 若父类模板有改动,则子类均要同步更改。
举例:安装电脑,具体代码实现详情见:
3.15 命令模式
命令模式是一种行为型的软件设计模式,行为请求者通过发起命令,使得行为实现者执行命令要求的行为。通常行为请求者和行为实现者之间是强耦合的,而命令模式能很好地将其解耦。
命令模式的优点:
- 解耦。行为请求者和实现者之间的强耦合关系解除。
- 良好扩展性。扩展命令只需要添加一个子类即可,满足开闭原则。
- 有效管理命令。如记录、撤销、组合等。
命令模式的缺点:
- 命令增加,子类数量也增多。
举例:军队指挥,具体代码实现详情见:
3.16 迭代器模式
迭代器模式是一种行为型的软件设计模式,提供一种方法能顺序访问聚合对象中的各个元素,而又不暴露其内部。
我们使用的聚合对象各种各样,比如vector、list、tree、map等等,既然是聚合,那就有访问其个体的需要。而遍历访问这个行为可能有深度优先、广度优先、顺序遍历、逆序遍历等等,迭代器的意义就是将这个行为抽离封装起来,这样客户端只需要调用合适的迭代器,来进行对应的遍历,而不用自己去实现这一行为。
迭代器模式的优点:
- 符合单一职责原则。将遍历行为抽离成单独的类。
- 符合开闭原则。添加新集合或者新迭代器,不改变原有代码。
- 便于扩展多种遍历行为。
- 访问数据又不暴露内部。
迭代器模式的缺点:
- 若对聚合对象只需要进行简单的遍历行为,那使用迭代器模式有些大材小用。
- 系统复杂性提高,类数量较多。
举例:容器遍历,具体代码实现详情见:
3.17 中介者模式
中介者模式是一种行为型的软件设计模式,也称为仲裁者模式,顾名思义,该模式的作用就是中介,帮助其他类进行良好的交流。
类之间关系混乱的时候,会有很强的耦合性。就拿租房为例,如果有10个租客,5个房东,他们各自单线联系,那么他们之间的交流会相对混乱,维护起来也复杂的多。此时出现了一个房产中介,它将租客和房东的信息整合起来,由中介安排合适的租客和房东交流,那么交流就变得高效且清晰,当然也要付费的(就像增加中介类的代码维护开销一样)。
中介者模式的优点:
- 解耦。中介的存在使得同事对象间的强耦合关系解除,它们可以独立地变化而不会影响到整体,便于被复用。
- 良好扩展性。交互行为发生改变,只需要扩展中介即可。
- 集中交互,便于管理。
中介者模式的缺点:
- 中介者的职责很重要,且复杂。
举例:房屋租赁,具体代码实现详情见:
3.18 观察者模式
观察者模式是一种行为型的软件设计模式,定义对象间的一种一对多的依赖关系,当被观察者状态发生改变时,所有观察者都做出相应改变。Qt中的信号与槽就是一种典型的观察者模式。
观察者模式的优点:
- 耦合双方依赖于抽象,不需要了解具体。
- 提供了稳定的信息更新传递机制。
- 良好扩展性。
观察者模式的缺点:
- 当观察者过多时,挨个通知观察者消息可能会花费较长时间。
- 某个观察者出现卡顿,可能影响整个进程,一般采用异步机制解决,但同时也要注意程序安全。
举例:开车等红绿灯,具体代码实现详情见:
3.19 备忘录模式
备忘录模式是一种行为型的软件设计模式,在不破坏封装的前提下,获取一个对象的内部状态,并在对象外保存该状态,当对象需要恢复到该状态时,对其进行恢复。
备忘录模式的优点:
- 良好封装性。发起人对象中的内部状态被保存在备忘录中,也只能由自己读取,对其他对象起到了屏蔽作用。
- 提供了状态恢复机制。类似于游戏存档读档。
- 简化了发起人职责。发起人状态的存储和获取,被分离出去了。
备忘录模式的缺点:
- 资源消耗较大,对发起人对象不同内部状态的存储,会导致开销增加。
举例:存读游戏进度,具体代码实现详情见:
3.20 状态模式
状态模式是一种行为型的软件设计模式,当一个对象的内在状态改变时,其行为也随之改变。就像玩游戏的时候,不同的buff状态,角色会有不同的伤害、技能等等。
当控制一个对象状态的条件表达式过于复杂时,很适合用该模式,将复杂的判断逻辑转移到表示不同状态的系列类中,能将逻辑大大简化。
状态模式的优点:
- 良好封装性。每个状态的行为被封装到对应类中。
- 便于维护。减少了if else或switch语句的出现,适用于条件判断复杂的场景。
- 良好扩展性。添加状态更便捷。
状态模式的缺点:
- 状态数量增加,类数量也会增加,对开发者要求较高。
- 状态少时,应用状态模式会显得冗余。
举例:开关灯,具体代码实现详情见:
3.21 责任链模式
责任链模式是一种行为型的软件设计模式,对象内存在对下家的引用,层层连接形成了一条责任链,请求的信息在链上传递直到某个对象决定处理该信息。
责任链模式的优点:
- 请求者和接收者松耦合。请求者只需要发送请求,不关心由谁处理怎么处理;接收者只需要处理自己该处理的,剩下的交给责任链上的其他职责处理。
- 比较灵活。责任链上各个职责对象,可以灵活排序或组合,以应对不同场景。
责任链模式的缺点:
- 性能易受影响。当责任链过长时,对请求的处理效率不够高。
- 不一定确保请求完整处理。每个职责只对自身部分负责,有可能请求走完整个责任链,也没有完全处理。
举例:申请批假,具体代码实现详情见:
3.22 访问者模式
访问者模式是一种行为型的软件设计模式,表示一个作用于某对象结构中的各元素的操作。使得在不改变各元素类的前提下,能定义作用于这些元素的操作。
该模式适合数据结构相对稳定且算法又易变化的系统。数据结构是被访问者,算法操作相当于访问者。
访问者模式的优点:
- 良好扩展性。扩展对元素的操作,只需要添加访问者。
- 满足单一职责原则。相关的操作封装为一个访问者,使得访问者职责单一。
- 解耦。数据结构自身和作用于它的操作解耦合。
访问者模式的缺点:
- 不易增加元素类。每增加一个元素类,访问者的接口和实现都要进行变化。
- 违背了依赖倒置原则。访问者依赖的是具体元素而不是抽象元素。
- 破坏封装。访问者可以获取被访问元素的细节。
举例:市长视察学校和企业,具体代码实现详情见:
3.23 解释器模式
解释器模式是一种行为型的软件设计模式,定义了一个解释器,来解释给定语言和文法的句子。也可以理解为翻译吧,比如1+1,翻译为一加上一,等于二,这样就做成了一个简单的加法计算器。
解释器模式的优点:
- 良好扩展性。语法的翻译通过类来实现,扩展类可以扩展其解释能力。
- 易实现。语法树中每个表达式节点类具备一定相似性,实现起来相对容易。
解释器模式的缺点:
- 执行效率低。解释器中通常有大量循环和递归语句,当被解释句子较复杂时,程序的性能受到较大影响。
- 类膨胀。规则较多时,类数量也膨胀。
举例:实现简单的加减法计算器,具体代码实现详情见:
四、总结
上述讲了这么多关于设计模式的内容,希望我举的例子能帮助你相对容易地理解设计模式。每个模式的链接点进去,都是专门的一篇文章,且附带了完整详细简单的源码,相信多少会给你带来一些帮助!
如果文章帮助到你了,可以点个赞让我知道,我会很快乐~加油!