状态(state)模式:行为型模式
-
模式:
- 把状态和状态对应的行为分离出来,把每个状态对应的功能封装在一个独立的类里面,选择不同处理的时候,其实就是在选择不同的状态处理类;
- 外部面向状态类的统一接口编程;
- 状态的维护和转换控制:
- 上下文中维护转换,适用于状态转换规则一定、不需要拓展的场景;
- 状态处理类中指定后续状态,适用于需要较强的灵活性的场景;
-
特点:
- 状态决定行为,行为具有平行性(互相独立、没有关联),但不具有平等性(可以替换),所以与策略模式不同;
- 客户端只与上下文交互、不负责状态维护,上下文持有状态、不负责具体状态功能;
- 创建和销毁状态的时机:
- 需要使用时创建,使用完之后销毁,适用于进入的状态在运行时不可知、上下文较为稳定的场景;
- 提前创建且始终不销毁,适用于状态改变频繁、状态存储着大量数据信息的场景;
- 延迟加载和缓存合用,适用于无法确认状态改变是否频繁、数据量大小的场景;
- 根据状态来分离和选择行为;
-
优点:
- 简化应用逻辑控制;
- 更好地分离状态和行为;
- 更好地扩展性;
- 显式化地进行状态转换;
-
缺点:
- 一个状态对应一个状态处理类,会使程序引入太多的状态类,使程序变得杂乱;
-
使用场景:
- 对象的行为取决于它的状态,而且对象必须在运行时根据状态来改变它的行为;
- 操作中有庞大的多分支语句,而且这些分支依赖于该对象的状态;
备忘录(memento)模式:行为型模式
-
模式:
- 不破坏对象封装性的前提下,来保存和恢复对象的状态;
- 真正的备忘录对象为原发器的私有内部类;
- 保存和恢复内部状态;
-
特点:
- 捕获对象内部状态,是为了在以后的某个时刻,将该对象的状态恢复到备忘录所保存的状态;
- 备忘录对象需要保存在原发器对象之外,原发器对象一般会包含备忘录对象的实现,也会提供按照外部要求来恢复内部状态到某个备忘录对象记录的状态的方法;
- 管理者能存取备忘录对象,但不能访问备忘录对象内部的数据;
-
窄接口和宽接口:
- 窄接口:管理者只能看到备忘录的窄接口,没有任何方法,只是类型标识;
- 宽接口:原发器能看到宽接口,允许访问所需的数据;
-
优点:
- 更好的封装性:虽然对象保存在原发器对象外部,但窄接口不提供任何方法;
- 简化了原型器;
- 窄接口和宽接口;
-
缺点:
- 基本实现方式是缓存备忘录对象,如果需要缓存的数据量很大,或者频繁地创建备忘录对象,开销是很大的;
-
使用场景
- 必须保存一个对象在某一时刻的全部或部分状态,方便在以后需要的时候恢复;
- 需要保存对象的内部状态,但是用接口会破坏对象的封装性;
享元(flyweight)模式:结构型模式
-
模式:
- 解决细粒度对象太多、存在大量重复数据的现象,方案是缓存这些重复对象的数据,让这些对象只出现一次;
- 需要注意缓存数据与真实数据的同步问题,所以不能缓存变化频繁的数据;
- 享元:把内部状态分离出来共享;
- 享元工厂:管理享元,控制外部对内部状态的共享,一般是单例,用 Map 存放缓存的享元对象;
- 分离与共享
-
特点:
- 分离变与不变:把一个对象的状态分为不变的内部状态和可变的外部状态,然后通过分享不变的部分,达到减少对象数量并节约内存的目的;
- 共享与不共享:享元对象有共享和不共享之分;
- 内部状态和外部状态:在享元对象内部的状态才被缓存和共享,内部状态和外部状态是独立的;
- 通常是在第一次向享元工厂请求获取共享对象的时候,进行共享对象的初始化,并且是在享元工厂内部实现;
-
缓存的问题:
- 缓存时间长度的问题;
- 缓存数据和真实数据的同步问题;
- 缓存的多线程并发控制问题;
-
实例池:
- 享元工厂一般都包含有享元对象的实例池;
- 实例池值缓存和管理对象实例的程序,通常实例池会提供对象实例的运行环境,并控制对象实例的生命周期;
- 两个难点:动态控制实例数量、动态分配实例给外部使用;
-
管理享元对象
- 引用计数
- 垃圾回收
-
优点:
- 减少对象数量,节省内存空间;
-
缺点:
- 维护共享对象需要额外开销
-
使用场景:
- 应用使用了大量细粒度对象;
- 对象的大多数状态可以转变为外部状态(例如通过计算得到);
- 可以用相对较少的共享对象取代很多组合对象,可以使用享元模式共享对象,然后组合对象来使用这些共享对象;
解释器(Interpreter)模式:行为型模式
-
模式:
- 解决通用问题:
- 设计一个简单的表达式语言,在客户端调用解析程序时,传入用一个表达式语言描述的一个表达式,然后把这个表达式通过解析器的解析,形成一个抽象的语法树;
- 解析完成后,自动调用解释器解释抽象语法树,并执行每个节点所对应的功能;
- 定义文法:解释器模式按照文法解析并执行相应功能;
- 分离实现,解释执行;
- 解决通用问题:
-
解析器和解释器:
- 解析器:指的是把描述客户端调用要求的表达式,通过解析,形成一个抽象语法树的程序;
- 解释器:解释抽象语法树,并执行每个节点对应的功能的程序;
-
特点:
- 理论上来说,只要能用解释器对象把符合语法的表达式表示出来,而且能够构成抽象的语法树,那都可以使用解释器模式来处理;
- 一般一个解释器处理一条语法规则,但一条语法规则可以对应多个解释器对象;
- 上下文具有公用性;
- 解析器实现构建语法树,解释器实现解释操作;
-
解析器的实现思路:
- 把客户端传递来的表达式进行分解,分解成为一个个的元素,并用一个对应的解析模型来封装这个元素的一些信息;
- 根据每个元素的信息,转换成相对应的解析器对象;
- 按照先后顺序,把解析器对象组合起来,得到抽象语法树;
- 为什么要用两步实现分解和转换成对象:
- 功能分离,不让一个方法的功能过于复杂;
- 可维护性,语法复杂后直接转换很杂乱;
-
优点:
- 易于实现语法:一个语法规则用一个解释器对象来解释执行;
- 易于扩展新的语法:扩展信誉法师只需要创建相应的解释器对象就可以了;
-
缺点:
- 如果语法特别复杂,构造抽象语法树的工作就会间距,对于复杂的语法,使用语法分析程序或编译器生成器可能会更好一点;
-
使用场景:
- 当有一个语言需要解释执行,并且可以将该语言中的句子表示为一个抽象语法树的时候,可以考虑使用解释器模式;
- 语法相对应该比较简单,效率要求不是很高;
装饰(decorator)模式:结构型模式
-
模式
- 透明地给一个对象增加功能(比子类更为灵活),并实现功能的动态组合;
- 不能改动原有对象;
- 定义一个抽象类,让这个类实现与被装饰对象相同的接口,然后在具体实现类中,转调被装饰的对象,在转调前后添加新的功能;
- 思考起点:(面向对象的设计中,有一条基本的规则就是)尽量使用对象组合,而不是对象继承来扩展和复用功能;
- 动态组合:把复杂功能简单化、分散化;
-
特点:
- 灵活性:
- 采用对象组合的方式,在不同的装饰器中实现不同的装饰;
- 可以动态地位墨迹个对象添加功能,而不是对整个类添加功能;
- 灵活性:
-
对象组合:
- 可以有选择性地复用功能;
- 在转调前后,可以实现一些功能处理,并不被被组合对象知道;
- 可以组合拥有多个对象的功能;
- 何时创建被组合对象的实例:
- 在属性上直接定义并创建需要组合的对象实例;
- 在属性上定义一个变量,来表示持有被组合对象的实例,具体实例从外部传入,也可以通过 IoC/DI 容器来注入;
-
装饰器
- 装饰器实现了对被装饰对象的某些装饰功能,可以在装饰器中调用被装饰对象的功能,获取相应的值,这其实是一种递归调用;
- 装饰器中还可以根据需要选择是否调用被装饰对象的功能;
- 装饰器一定会要和组件类有一致的接口、同一个外观;
- 退化形式:不需要设计装饰器的抽象类,直接在装饰器中实现和组件一样的接口;
-
AOP:面向方面编程
- AOP 为开发者提供了一种描述横切关注点的机制,并能够自动将横切关注点植入到面向对象的软件系统中,从而实现了横切关注点的模块化;
- AOP 能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性;
- 可以使用装饰模式,把一些公共的功能透明地添加回业务功能模块中去,做出类似 AOP 的效果;
-
优点
- 比继承更灵活:继承是静态的,而且一旦继承所有子类都有一样的功能,二装饰模式把功能分离到每个装饰器中,运行时动态地组合功能;
- 更容易地复用功能;
- 简化高层定义:进行高层定义时,只需要定义最基本的功能,需要使用时组合相应地装饰器来完成所需功能;
-
缺点
- 会产生很多细粒度对象;
-
使用场景:
- 需要在不影响其他对象地情况下,以动态、透明地方式给对象添加职责;
- 不适合用子类来扩展的时候(如,扩展功能需要的子类太多,造成子类数目爆炸性增长),可以考虑使用装饰模式;
职责链(chain of responsibility)模式:行为型模式
-
模式:
- 多个对象处理请求,沿着一条链传递该请求,直到有一个对象处理为止;
- 实现:
- 定义一个所有处理请求对象都需要继承的实现的抽象类,可以随时切换新的实现;
- 每个处理请求对象只实现业务流程中的一步业务处理;
- 职责链动态地组合处理请求的对象,把它们按照流程动态地组合起来,并依次调用;
- 分离职责,动态组合;
-
特点
- 请求者和接受者解耦;
- 隐式接收者:对于请求者而言,并不知道最终的接收者是谁,但一般情况下总有一个对象来处理;
- 运行时期动态决定谁来处理请求;
- 可以在职责链末端始终加上一个不支持此功能处理的职责对象;
-
功能链
- 指责对象负责处理请求某一方面的功能,处理完成后,不是停止、而是继续向下传递请求,当请求通过很多职责对象处理后,功能也就完成了;
-
优点
- 请求者和接收者松散耦合;
- 动态组合职责,灵活地给对象分配职责;
- 潜在优点:当职责功能是公共功能时,增强职责功能的可复用性;
-
缺点:
- 产生很多细粒度对象;
- 不一定能被处理;
-
使用场景:
- 如果有多个对象可以处理同一请求,但是具体使用哪个对象是运行时动态确定的;
- 想在不明确接收者的情况下,向多个对象的其中一个提交请求;
- 动态地指定处理一个请求的对象集合;
桥接(bridge)模式:结构型模式
-
模式
- 将抽象部分和实现部分分开,使他们可以独立地变化;
- 桥接在程序上体现了在抽象部分用友实现部分的接口对象:抽象部分拥有实现部分的接口对象,抽象部分就可以通过这个接口来调用具体实现部分的功能;
- 广义桥接:桥接模式就是对“面向抽象编程”设计原则的扩展;
- 分离抽象和实现,很好地体现了开闭原则和多用对象组合、少用对象继承;
-
特点
- 独立变化;
- 动态变换功能;
- 退化的桥接模式:依然要保持分离机制;
- 可以用来实现多层架构
-
优点
- 分离抽象和实现部分,从而极大地提高了系统的灵活性、有助于对系统分层;
- 更好地扩展性;
- 可动态地切换实现;
- 可以减少子类的数量:如果采用继承的实现方法,需要两个纬度上的可变化数量的乘积个子类,而采用桥接模式来实现,需要两个纬度上的可变化数量的和个子类;
-
使用场景:
- 不希望在抽象部分和实现部分采用固定的绑定关系;
- 抽象部分和实现部分都能够扩展的情况;
- 实现部分的修改不会对客户产生影响,可以说对客户是透明的;
- 采用继承的实现方案会导致产生很多子类;
访问者(visitor)模式:行为型模式
-
模式
- 运行期间把功能动态地添加到对象结构中去;
- 实现基本思路:
- 首先定义一个接口来代表要新加入的功能,为了通用,也就是定义一个通用的功能方法来代表新加入的功能;
- 在对象结构上添加一个方法,作为通用的功能方法,也就是可以代表被添加的功能,在这个方法中传入具体的实现新功能的对象;
- 在对象结构的具体实现对象中实现这个方法,回调传入具体实现新功能的对象,就相当于调用到新功能上了;
- 最后再提供一个能够循环访问整个对象结构的类,让这个类来提供符合客户端业务需求的方法,来满足客户端调用的需要;
- 预留通路,回调实现;
-
特点
- 访问者模式能给一系列对象透明地添加功能;
- 始终要把一系列对象都调用到;
- 访问者地访问方法和被访问者接受访问的方法,构成一个调用的通路;
- 可以定义一个 ObjectStructure 负责遍历访问一系列对象中的每个对象;
-
优点:
- 好的扩展性:能够再不修改对象结构中地元素地情况下,为对象结构中的元素添加新的功能;
- 好的复用性:可以通过访问者来定义整个对象结构通用的功能,从而提高复用程度;
- 分离无关行为:可以通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者地功能都比较单一;
-
缺点:
- 对象结构变化很困难:不适用于对象结构中的类经常变化的情况,因为对象结构发生了改变,访问者的接口和访问者的实现都要发生相应的改变,代价太高;
- 破坏封装:访问者模式通常需要对象结构开放内部数据给访问者和 ObjectStructure,这破坏了对象的封装性;
-
使用场景:
- 如果想对一个对象结构实施一些依赖于对象结构中的具体类的操作,可以使用访问者模式;
- 如果相对一个对象结构中的各个元素进行很多不同的而且不相关的操作,为了避免这些操作使类变得杂乱,可以使用访问者模式,把这些操作分散到不同的访问者对象中去,每个访问者对象实现同一类功能;
- 如果对象结构很少变动,但是需要经常给对象结构中的元素对象定义新的操作,可以使用访问者模式;