重新领略设计模式之美
本文主要讲解如何设计模式的一些优缺点和适用场景以及一些概念信息
首先我们看一下设计模式的总览
接下来我们开始逐个分析每个设计模式的优缺点和概念
单例模式
概念:
保证一个类仅有一个实例,并提供一个访问它的全局访问
优点:
- 提供了对唯一实例的受控访问
- 允许可变数目的实例
- 避免对共享资源的多重占用
缺点:
- 扩展麻烦
- 单例类的职责过重,在一定程度上违背了“单一职责原则”
适用场景
- 需要频繁实例化然后销毁的对象
- 有状态的工具类对象。
- 频繁访问数据库或文件的对象。
工厂模式
概念:
定义一个用于创建对象的接口,让子类决定实例化哪一个类,工厂模式是一个类的实例化延迟到子类
优点:
- 将职责进行分类
缺点:
- 扩展性差
- 不支持不同的产品需要不同额外参数的时候
适用场景
- 消费者不关心它所要创建对象的类(产品类)的时候。
- 消费者知道它所要创建对象的类(产品类),但不关心如何创建的时候
抽象工厂模式
概念:
提供一个创造一系列或相对依赖关系的接口,而无需制定它们具体的类
优点
- 在类的内部解决管理多个产品的问题
缺点
- 太臃肿,管理的类太多
- 产品族的扩展将是一件十分费力的事情,假如产品族中需要增加一个新的产品,则几乎所有的工厂类都需要进行修改
适用场景:
- 一个系统要独立于它的产品的创建、组合和表示时
- 一个系统要由多个产品系列中的一个来配置时
- 需要强调一系列相关的产品对象的设计以便进行联合使用时
- 提供一个产品类库,而只想显示它们的接口而不是实现时
建造者模式
概念:
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创造不同的表示
优点
- 将过程隐藏,高内聚的前提下降低了耦合度
缺点
- 缺少大批量的相同的建造过程
适用场景
- 就是一个继承体系中,如果存在着多个等级结构(即存在着多个抽象类),并且分属各个等级结构中的实现类之间存在着一定的关联或者约束
原型模式
优点
- 复制结构和数据/原型模式是在内存中二进制流的拷贝,要比直接new 一个对象性能好很多
缺点
- 它的优点也是缺点,直接在内存汇总拷贝,构造函数是不会执行的
适用场景
- 需要一个类的大量对象的时候
- 如果一个对象的初始化需要很多其他对象的数据准备或其他资源的繁琐计算
适配器模式
概念
将一个类的接口转换成客户希望的另外一个接口,适配器模式使得原本由于接口不兼容而不能在一起工作的接口可以一起工作
优点
- 将目标类和适配者类解耦
- 增加了类的透明性和复用性,将具体的实现封装在适配者类中,对于客户端类来说是透明的,而且提高了适配者的复用性/灵活性和扩展性都非常好,符合开闭原则
缺点
- 由于C#不支持多重继承,所以最多只能适配一个适配者类,而且目标类必须是抽象类
- 采用了类和接口的“双继承”实现方式,带来了不良的高耦合。
适用场景
- 统一多个类的接口设计
- 兼容老版本接口
- 适配不同格式的数据
桥接模式
概念
将抽象部分和实现部分分开,使他们都可以独立的变化
优点
- 减少耦合,用聚合来代替继承
缺点
- 增加系统的理解与设计难度
适用场景
- 系统可能有多角度分类,每一种分类都有可能变化。
组合模式
概念
将对象组合成树形结构以表示'部分-整体'的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性
优点
- 一致地使用组合结构和单个对象
缺点
- 建含有特定对象的类难以实现
适用场景
- 树形结构,需求中是体现部分与整体层次的结构时,并且希望用户可以忽略组合对象与单个对象的不同,统一地使用结构中的所有对象时,考虑用组合模式
装饰模式
概念:
动态的给一个对象添加一些额外的职责。就增加功能来说,装饰模式比生成子类更加灵活
优点
- 简化原有的类,有效的解耦,
缺点
- 把功能拆分太细,容易出错
适用场景
- 当系统需要新功能的时候,新加的东西仅仅是为了满足一些只在某种特定情况下才会执行的特殊行为的需要
外观模式
概念
为子系统中的一组接口提供一致的界面,外观模式定义了一个高层接口,这个接口使的这一子系统更加容易使用
优点
- 降低耦合度
缺点
- 降低了灵活性
使用场景
- 设计初期,应该有意识的将不同的层分离
- 开发期,子系统往往因为不断地重构演化而变得越来越复杂
- 维护期,系统难以维护和扩展。
享元模式
概念
为运用共享技术有效地支持大量细粒度的对象
优点
- 节约存储空间
缺点
- 需要维护一个记录了系统已有的所有享元的列表,这本身需要耗费资源,享元模式使得系统更加复杂。
适用场景
- 一个应用程序使用了大量的对象,而大量的这些对象造成了很大的存储开销
代理模式
概念:
为其他对象提供一种代理以控制对这个对象的访问
优点
- 降低耦合度,扩展性好
缺点
- 因为有中间层的存在,导致处理速度变慢
适用场景
- 用来控制真实对象访问时的权限
- 需要对当前类做一些前置或者后置操作(autofuc)
观察者模式
概念
定义对象间的一种一对多的依赖关系,当一个对象的状态正在发生改变的时候,所有依赖于它的对象都得到通知并自动改变
优点
- 可以实现表示层和数据逻辑层的分离
- 在观察目标和观察者之间建立一个抽象的耦合
- 支持广播通信且符合开闭原则
缺点
- 将所有的观察者都通知到会花费很多时间/如果存在循环调用可能导致系统崩溃/没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅知道观察目标发生了变化
适用场景
- 一个抽象模型有两个方面,其中一个方面依赖于另一个方面,将这两个方面封装在独立的对象中使它们可以各自独立地改变和复用
模板方法
概念
定义一个操作的算法骨架,而将一些步骤延迟到子类中,模板方法使的子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤
优点
- 首先从设计上将变与不变区分开,将不变的部分抽取出来定义在父类中/能够实现算法骨架的统一,通过切换不同的子类类实现不同的功能,很符合开闭原则,里氏替换原则
缺点
- 类数目的增加,每一个抽象类都需要一个子类来实现,这样导致类的个数增加
- 类数量的增加,间接地增加了系统实现的复杂度。
适用场景
- 需要固定的算法骨架,实现一个公共部分,将可变的部分交给子类去实现;
命令模式
概念
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,可以对请求进行排队或记录请求日志,以及支持可撤销的操作
优点
- 降低对象之间的耦合度/调用同一方法实现不同的功能
缺点
- 如果子类太多,就会导致Command非常庞大
适用场景
- 当需要先将一个函数登记上,然后再以后调用此函数时,就需要使用命令模(其实就是回调函数)
状态模式
概念
允许一个对象在其内部活动状态改变它的行为,让对象看起来似乎修改了它的类
优点
- 封装了转换规
- 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块
- 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数
缺点
- 状态模式的使用必然会增加系统类和对象的个数/状态模式对“开闭原则”的支持并不太好,如果需要进行切换某个状态,需要去源码中进行修改
适用场景
- 对象的行为依赖于它的状态(属性)并且可以根据它的状态改变而改变它的相关行为/代码中包含大量与对象状态有关的条件语句
职责链模式
概念
使多个对象都有机会处理请求,从而避免请求的发送者和接受者之前的耦合关系,将这些对象形成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止
优点
- 将请求和处理分开,实现解耦,提高系统的灵活性/简化了对象,使对象不需要知道链的结构
缺点 - 当职责链过长,性能会受到影响
- 调试不方便。采用了类似递归的方式,调试时逻辑可能比较复杂
- 不能保证请求一定被接收
使用场景
- 多个对象可以处理同一个请求
- 可动态指定一组对象处理请求
解释器模式
概念
给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言的句子
优点
- 可扩展性高
缺点
- 解释器模式会引起类膨胀
- 使用了大量的循环和递归,效率是一个不容忽视的问题
适用场景
- 可以将一个需要解释执行的语言中的句子表示为一个抽象语法树
- 一个简单语法需要解释的场景
中介者模式
概念
用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显示的相互引用,从而使其耦合松散,而且可以独立的改它们之间的交互
优点
- 解耦。把同事类原来一对多的依赖变成一对一的依赖,降低同事类的耦合度,同时也符合了迪米特原则。
缺点
- 中介者模式把业务流程和协调都写在中介者,当同事类越多,中介者的业务就越复杂,造成不好管理的弊端
- 如果要增减同事类,必须得修改抽象中介者角色和具体中介者角色类。
适用场景
- 中介者模式一般应用于一组对象以定义良好但是复杂的方式进行通信的场合,以及想定制一个分布在多个类中的行为,而又不想生成太多的子类的场合。
访问者模式
概念
表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变各元素的类的前提下定义这些元素的新操作
优点
- 对于原来的类层次增加新的操作只需要实现一个具体访问者角色,而不必改变整个类层次。每个具体的访问者角色都对应于一个相关操作。
缺点
- 不适合具体元素角色经常发生变化的情况。每增加一个元素类都需要修改访问者类(也包括访问者类的子类或者实现类),修改起来相当麻烦
适用场景
- 当一个对象结构包括很多类对象,它们有不同的接口,而系统要求这些对象实施一些依赖于某具体类的操作时,就可以使用访问者模式。
策略模式
概念
定义一系列的算法,将他们一个个封装起来,并且使它们可相互替换,当前模式使得算法可独立于使用它的客户而改变
优点
- 策略模式符合开闭原则
- 避免使用多重条件转移语句,如if...else...语句、switch 语句/使用策略模式可以提高内部的保密性和安全性。
缺点
- 需要客户端知道的太多。必须知道所有的策略,并且自行决定使用哪一个策略类
- 代码中会产生非常多策略类,增加维护难度。
适用场景
- 不同会员等级购买产品价格计算,比如对不同消费用户做内容营销的时候,都是可以使用策略模式
备忘录模式
概念
不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可将该对象回复到原先保存的状态
优点
- 可以避免暴露一些只应由源发器管理却又必须存储在源发器之外的信息,而且能够在对象需要时恢复到先前的状态。
缺点
- 如果源发器在生成备忘录时必须复制并存储大量的信息,或者客户非常频繁地创建备忘录和恢复源发器状态,可能会导致非常大的开销。
适用场景:
- 需要保存和恢复数据的相关状态场景
- 提供一个可回滚(rollback)的操作。
迭代器模式
概念
提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示
优点
- 分离了集合对象的遍历行为
缺点
- 类的个数成对增加
适用场景
- 需要为一个聚合对象提供多种遍历方式/访问一个聚合对象的内容而无须暴露它的内部表示
总结
虽然看完了设计模式,但这并不是结束,而是刚刚开始,在以后的编码过程中,会结合相对应的设计模式来优化和设计代码,最终达到学有所用
设计模式一遍生,二遍熟,三遍醇,也希望以后回顾是能发现设计模式更美的地方(设计模式虽好,可不要乱用哦)
文中给出了设计模式相对应的一些场景,但是绝对不止这些,欢迎各位给出宝贵的意见,也非常期待大家和我交流更多关于设计模式的心得(部分场景参考园子中的一些帖子信息)
如有哪里讲得不是很明白或是有错误,欢迎指正
如您喜欢的话不妨点个赞收藏一下吧🙂
个人微信
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步