设计模式整体回顾
终于写完了经典的23中设计模式,往事不堪回首。
这篇对这23中做一个总结,以便在后续需要时方便识别哪种是需要的设计模式。
# 创建型
描述对象如何创建,是为了将对象的创建与使用分离。
包括6种:单例、原型、简单工厂、工厂方法、抽象工厂、建造者。
# 简单工厂
简单的将对象的创建与使用分离 特点:
- 产品是有限的、已知的。
- 工厂类可以直用静态方法,所以可隐藏构造函数(构造函数私有化)
- 创建方法理论上只依赖产品列表,所以可以创建各种实例,不限于只一类。(这是一个危险的实现)
问题:
- 添加新产品需要修改已经完成的工厂方法。
- 客户需要知道产品列表和功能直接的关系,增加复杂度。
# 工厂
特点/优点:
1、工厂类可以不实例化,使用静态工厂类。
2、延时创建,使用类再不确定是否要创建产品时,可以通过拿着产品的工厂方法。再需要时再创建。 实现延时创建。
问题:
1、随着产品的增加,工厂也不断的增加,可能造成对象的爆发。
2、客户需要关注每个工厂的不同才能获得想要的产品,可能增加维护成本。
# 抽象工厂
抽象工厂其实并不是一个复杂概念,其实抽象工厂就是工厂模式的一般现象,工厂模式就是抽象工厂的个例。
特点/优点:
抽象工厂想要新增一个套餐是简单的,但是想要在套餐中新增一个项就比较不友好。
问题:
抽象工厂适用于整套的替换场景中。
# 建造者
优点: 建造者是为了将对应的构造和对象的使用分离;
主要应对复杂的创建场景,如 参数不固定,参数见相互影响 等。
条件定义: 一般当一个类的构成超过4个参数,且部分参数可有可无时,就认为这个类的构造已经足够复杂;可以考虑使用建造者模式,来代替多个构造函数的定义。
问题: 有时,我们只有一个类的构造十分复杂;使用建造者模式就导致类膨胀的厉害。
这时我们可以考虑使用 局部类,来实现 简化的建造者模式。
# 单列模式
种类 | 实现 | 优点 | 缺点 |
---|---|---|---|
饿汉单例 | 1、使用静态区对象,确保只有一份实例 | 1、多线程,单线程可以用统一的方式处理 2、无加锁,效率高 | 1、程序运行时需要加载暂时无用的内存,可能影响效率 2、静态对象的初始化由系统决定,如果初始化依赖静态对象,可能导致异常。 |
懒汉单例 | 1、通过判空的方法,确保只有一份实例 | 1、程序运行时不用加载多余的数据 | 1、第一次加载可能不够快 2、代码相对复杂 |
# 原型模式
原型模式的本质是通过深copy的方式生成对象,不用受构造函数的约束(可不用引入具体头文件)。
优点 / 使用场景:
- 性能优化的场景中,当一共从头建造是需要消耗大量资源(如需要大量的IO操作,权限申请等)
- 安全要求场景中,不向外暴露头文件,再一写情况下比较安全。
组合使用方式:
- 可以和工厂模式结合使用,作为工厂模式创建
缺点:
- 对于类中含有容器对象【如组合模式】不够友好
- 对类中对象由外部传递时,不够友好
# 结构型
描述类或对象如何组织成更大结构 包括7种:代理、适配器、桥接、装饰、外观、享元、组合。
# 装饰
使用场景:
不想增加子类(或者需要增加子类的数量比较多)时,想扩展类能力。
优点:
装饰类和被装饰类可以独立发展,而不会相互耦合。
有效的分割核心职责和非核心职责
可动态地扩展一个实现类的功能(【套娃】使用),减少了类的数量
注意点:
装饰模式一般是构造函数中传入被装饰的对象。
装饰模式是的目的是为了给现有对象增强能力,但是不管怎么增强,不会改变对象的本质。(这点很重要,后面会在代理中做比较)
缺点:
多层包装的装饰类,出BUG,要一层一层查。如:游戏角色的装扮,如果一共20层装饰者,工作量大。
# 代理
代理模式是一个自由度特别高的模式,也是一个不容易被辨识的模式。 场景:
客户和要使用的对象之间的复杂性,当我们规划了两个实体A和B后,发现:
1、A和B之间的调用关系复杂;
2、调用在业务上不属于A或者B的。
我们就需要代理来处理这部分的复杂性,代理通过添加中间层的方式在A和B之间添加了一层新的扩展封装。
spring aop 是现在比较有名的代理模式实现。
# 桥接
优点:
1、从两个维度拆分多重基础类,极大的减少了类个数。
2、两个维度可以独立变化,而不影响另一个维度。
缺点:
1、外在使用时会更复杂。
对比代理,装饰者,我们发现这三这外在使用时,有类似的使用方式。
这样对注重点做对比:
代理:增加不属于原对象的功能。
装饰者:对原有功能的增强,增强后还是原有功能。
桥模式:拆分两个维度,以简化原实现。
# 外观
外观模式通过提供统一的面板,隐藏了子系统、子模块的具体实现。系统的封装性和易用性得到提升,可以阻止新人对不熟悉模块的错误使用。
同时增加新的接口也需要修改面板,相对而言面板本身容易被影响,扩展性下降。
# 享元
享元模式的目的是通过减少对象的创建,来达到减少内存,提供效率的目的;
它设计的重点在于HashMap是使用,我们需要为HashMap找到一个唯一的特征值。
HashMap一般放在享元工厂中管理。
优点: 提高对象复用率,提高效率
适用场景:
- 大量相似或相同对象。
- 这些对象占用大量内存,或影响效率。
- 不同对象对外部执行无影响
使用时需要区分 内蕴状态 和 外蕴状态
内蕴状态(Internal State):存储在享元对象内部且不会随环境改变而改变。因此内蕴状态并可以共享。 外蕴状态(External State):是随环境改变而改变的、不可以共享的状态。
# 组合
组合模式又叫 “部分整体模式”,这个名字可以更好的表达这个模式想要解决的问题。
及:事物的 部分和整体具有高度相似性。
在组合模式中,每个节点的类定义中,都可以继续包含一组和自己相同的对象;
叶子节点的定义不是必须,但叶子节点一般是必然存在的(数据是有限的)。
# 适配器
1、从上述的可知适配器的本质是为了去处理由于某种原因,不能被修改代码的部分。如果我们可以修改目标代码,不建议使用适配器。
2、上述场景为单向调用场景;在实际的场景中还有很多是两个模块相互使用。同时,他们又是不可以被改动的情况。这时我们需要使用双向适配器。这时主要使用类适配器。
# 行为型
描述类或对象之间如何协作完成任务
包括11种:模板方法、策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录、解释器。
# 模板方法
如果行为的模板是相同的,可以用父类封装行为的框架;子类扩展可变的部分。
优点: 1、更好的代码复用性;2、父类封装控制,子类实现细节;实现更可控
缺点: 1、类的增加,导致实现更复杂
# 策略
策略模式的本质是对算法的封装,使算法的使用者和算法本身可以自由变化。
优点: 1、扩展性好;2、可以自由切换策略,对象复用性高
缺点: 所有算法都需要暴露给客户段,维护成本变高
可以考虑结合简单工厂,对策略本身也进行一层封装;减少暴露。
# 命令
命令模式主要实现了调用者和接受者之间的解构。
场景1: 1、调用者在一些不知道接受者的情况,或不能操作调用者的情况。
eg: 界面上有多个按钮控件,分别实现不同的业务功能。
一般界面控制使用一些公共组件;界面控件不知道具体业务使用类,且我们不能操作界面控件的内部实现。
场景2: 1、调用者和接受者执行的生命周期不同;接受者执行时,调用者已经被释放。
eg: 数据库系统的撤销、恢复、更新。
数据库根据命令进行撤销、恢复、更新;数据库系统的需要根据自身情况完成数据维护。如果调用者一直等等数据库的返回,可能导致阻塞。同时调用者也不可能提供 撤销、恢复 需要的状态,命令+备忘录可以很好的满足这里的需求。
同策略模式的区别:
简单类别:
命令模式是处理将不同的事情用同一种方式去统一调用,策略模式是处理同一件事情的不同处理方式。
命令模式:命令实例中自己拿着接收者实例。所以命令可操作的对象其实不拘束为一个对象,甚至可以不局限为同一类对象。
策略模式:策略实例无接受者实例,是对一件事的不同做法。
# 职责链
职责链模式 是为了 分离 发送方 和 接收方; 所以需要在设计时,也尽可能减少耦合。
纯的职责链模式:一个请求必须被某一个处理者对象所接收,且一个具体处理者对某个请求的处理只能采用以下两种行为之一:自己处理(承担责任);把责任推给下家处理。
但是小明的问题里还有一个是: 雪糕有点化了,这里用纯的职责链模式就不能胜任。及需要:
不纯的职责链模式:允许出现某一个具体处理者对象在承担了请求的一部分责任后又将剩余的责任传给下家的情况,且一个请求可以最终不被任何接收端对象所接收。
# 状态
状态模式是解决:当对象的行为随着对象的属性变化而变化的情况,采用的方式是将不同的行为进行封装。
什么时候需要状态模式: 当面类中代码中存在大量if判断,并且if判断的业务条件相同。
优点: 1、将于特定的条件相关的代码集中到一个类里面,有力的减少了if判断;且利用对一个业务统一修改。
2、无需修改上下文即可引入新的状态。
缺点:
1、状态模式需要上下文环境类有良好的设计,对设计要求较高。
# 观察者
优点: 1、建立了触发机制,为了解决一些响应式的业务流。
2、调用者和被调用者进行了抽象解耦,调用者将不知情自己将调用什么。
当有业务需要用【每当...... 就.....】 描述时,可以考虑使用观察者模式。
如果不希望调用者被阻塞,可以才有异步模式执行触发器。
局限: 1、需要避免循环调用 观察者模式也是一个需要谨慎使用的模式,由于观察者模式的响应式触发;导致难以在代码中追查到完整的业务流。
试想如果一个业务完全有触发器堆砌的程序,整个程序的业务就处于:A触发B,B触发C,C触发...的链式触发中。 当多个业务链有交叉时,如何让复杂业务不做循环调用这种简单要求也会变成世纪难题。
2、一个事件上挂的触发器太多,可能导致原来代码的效率下降。
3、观察者无法知道需要观察对象的状态,需要提供额外的能力实现。
# 中介者
中介者模式是一个不太常用模式,主体思想是将网状关系,转变为星形关系。
将网状关系的复杂性,收容到一个类中;
这使得其他类变得简单,同时也有中介者类复杂化的代价。
# 迭代器
迭代器模式又叫游标模式是一种常见模式,行为型设计模式的一种。几乎每种语言,会实现自己的迭代器。
在代码编程中,常常需要聚合对象来存放一组数据;
迭代器可以依次访问聚合对象内部的各个元素,同时又不暴露聚合对象的内部表示;
一般做业务开发时,并不需要实现迭代器模式;如果需要做公共组件,基础组件时,封装迭代器可能会被使用。
# 访问者
访问者模式有利于支持资源结构稳定,使用方式变化的场景;如资源为内部结构,不可直接操作;这时需要添加方法;
优点: 增加一个功能的实现类,影响很小。
缺点: 增加一个资源的实现类,影响较大。
# 备忘录
备忘录是为系统的运行提供了后悔药;让系统可以在需要的时机,可以回到希望的状态。
常见的应用场景:撤销恢复,游戏存档,数据库事务等。
当一个对象的所有信息都需要进入备忘录,我们可以思考使用 : 原型模式 + 备忘录模式
通过原型模式的自copy,我们不会丢失任何数据;并可以将 备忘录中 的备忘录使用者和备忘录统一。
# 解释器
解决发生频率很高,但规则可明细的场景。
优点:
良好的扩展性,可以不断定义新的表达式来实现新的业务。
缺点:
容易引起类膨胀,需要较强的类管理能力。