[杂七杂八的学习记录]《研磨设计模式》读书笔记(二)

184 阅读12分钟

中介者(mediator)模式:行为型模式

  1. 模式:

    1. 用中介对象与其他所有对象(同事对象)交互,同事对象间不直接交互;
    2. 同事对象不需要维护交互关系;

    image.png

    image.png

  2. 特点:

    1. 封装对象之间的交互,对象只在需要时通知中介者,中介者去主动处理其他对象(注意!);
    2. 中介者对象和同事对象直接是相互依赖的;
    3. 如何通知中介者:方法一在中介者中暴露通用的通知接口、同时对象在调用时会将自身当作参数传入、中介者可以获得同时对象的实例,方法二采用观察者模式;
    4. 封装交互;
  3. 广义中介者:

    1. 中介者和同事不再互相持有;
    2. 中介者可以实现为单例;
    3. 不需要同事元素的共同父类、不定义中介者接口;

    image.png

    image.png

  4. 优点:

    1. 松散耦合:使同事对象间松散耦合;
    2. 集中控制交互;
    3. 多对多关系变成双向的一对多;
  5. 缺点:

    1. 过度集中化,让中介者十分复杂、难以管理和维护;
  6. 使用场景:

    1. 一组对象之间的通信关系比较复杂;
    2. 一个对象引用很多对象、并直接交互,导致难以复用该对象;

代理(Proxy)模式:结构型模式

  1. 模式:

    1. 用代理代替具体的目标对象,需要时采取调用具体的目标对象;
    2. 实现上主要使用对象的组合和委托,也可以使用继承;
    3. (主要目的)控制对象访问;

    image.png

    image.png

    image.png

  2. 特点:

    1. 代理对象相当于外部对象和被代理对象的中转;
    2. 分类:

    image.png

    image.png

  3. 保护代理

    1. 权限问题;
  4. 使用场景

    1. 需要为一个对象在不同地址空间提供局部代表的时候,可以使用远程代理;
    2. 需要按需创建开销较大的对象是,可以采用虚代理;
    3. 需要控制对原始对象的访问时,可以采用保护代理;
    4. 需要在访问对象时执行一些附加操作时,可以使用智能指引代理;

观察者(observer)模式:行为型模式

  1. 模式:

    1. 当状态改变时,通知所有依赖它的对象;
    2. 依赖对象为观察者(Observer),被观察的对象为目标(Subject),一旦目标的状态发生改变,所有注册的观察者都会得到通知、做出相应的相应;
    3. 触发联动;

    image.png

    image.png

  2. 特点:

    1. 观察者依赖目标,目标不依赖观察者;
    2. 又被称为发布-订阅模式;
    3. 先修改数据、再触发通知;
    4. 当心相互观察造成的死循环;
    5. 推模型和拉模型:
  3. 推模型和拉模型

    1. 推模型目标对象主动向观察者推送详细信息,常用于广播;
    2. 拉模型在通知时只传递少量信息(如目标对象自身),观察者需要具体信息后可以主动去目标对象中获取;

    image.png

  4. 优点

    1. 解耦,观察者和目标之间只在抽象层面上耦合;
    2. 观察者和目标动态联动,可以注册、取消注册来控制观察者;
    3. 支持广播通信;
  5. 缺点:

    1. 广播通信可能引起无谓的操作;
  6. 使用场景:

    1. 当一个对象的操作依赖于另一个对象的操作;
    2. 更改一个对象时,需要连带改变其他的对象,而且不知道有多少对象需要改变;
    3. 当一个对象需要通知其他对象,但又希望两者松散耦合;

命令(command)模式:行为型模式

  1. 模式:

    1. 只发送命令、不关心接收者、也不关心具体如何实现;
    2. 用户调用 invoker,触发命令,命令和具体实现的接收者通过装配者绑定在一起;
    3. 使用不同的命令对象,参数化配置用户请求;
    4. 封装请求;

    image.png

    image.png

    image.png

  2. 特点:

    1. 把请求封装成对象(命令对象),并定义统一执行操作的接口;
    2. 命令对象可以被存储、转发、记录、撤销;
    3. 组装者:维护命令的“虚”实现和真实实现的关系;
    4. 发送请求的对象和真正实现的对象是解耦的;
  3. 可撤销操作:

    1. 补偿式:反向操作;
    2. 存储恢复式:记录操作前状态(备忘录模式);
  4. 宏命令:

    1. 宏命令:包含多个命令的命令,是一个命令的组合;
  5. 队列请求:

    1. 依次取出命令执行;
  6. 日志请求

    1. 请求队列日志化;
  7. 智能命令:

    1. 命令对象实现命令功能,不需要接收者;
    2. invoker 也可以实现命令功能,即“回调”;
  8. 优点

    1. 松散的耦合:发送命令的对象和实现命令的对象是解耦的;
    2. 动态控制,动态地对请求进行参数化、队列化和日志化等操作;
    3. 复合命令;
    4. 更好地扩展性;
  9. 使用场景:

    1. 需要抽象出需要执行的动作,并且参数化这些对象;
    2. 需要在不同的时刻指定、排列和执行请求,可以封装成命令对象,并将请求队列化;
    3. 需要支持取消;
    4. 需要支持崩溃后重新执行;
    5. 需要事务;

迭代器模式(iterator):行为型模式

  1. 模式

    1. 用统一的方式、顺序访问内部实现不同的聚合对象;
    2. 具体的聚合对象应该有不同的迭代器;
    3. 关键思想:把聚合对象的遍历和访问从聚合对象中分离出来,放入单独的迭代器中;
    4. 控制访问聚合对象中的元素;

    image.png

  2. 特点

    1. 迭代器延伸功能:

    image.png

    1. 内部迭代器和外部迭代器:内部迭代可以自己控制下一个元素的步骤,客户端将操作传递给迭代器,类似回调;外部迭代器由客户端来控制迭代下一个元素,比如客户端显式地用 next;
  3. 优点:

    1. 更好的封装性:可以顺序访问一个聚合对象的内容,而不暴露聚合对象的内部表示;
    2. 用不同的遍历对象遍历聚合对象:将聚合对象的内容和具体的迭代算法分离开,就可以通过使用不同的迭代器实例、不同的遍历对象来遍历聚合对象;
    3. 简化聚合的接口:聚合对象不需要定义迭代器的接口;
    4. 简化客户端调用:遍历不同的聚合对象有统一的接口;
    5. 同一个聚合上可以有多个遍历;每个迭代器可以保持自己的遍历状态;
  4. 翻页功能

    1. 纯数据库(时间换空间):每次拉一页数据;
    2. 纯内存(空间换时间):每次拉所有数据,存储在内存中;
    3. 数据库 + 内存:每次拉几页数据,存储在内存中;
    4. 每次翻页迭代下一页的数据;
  5. 使用场景

    1. 访问聚合对象内容、不暴露内部表示;
    2. 多种遍历方式访问;
    3. 遍历不同的聚合对象提供统一的接口;

组合(composite)模式:结构型

  1. 模式

    1. 不区分使用单个对象和组合对象;
    2. 引入抽象的组件对象,作为组合对象和叶子对象的父对象,这样在操作时不用区分是组合对象还是叶子对象;
    3. 关键:设计一个抽象的组件类,可以代表组件对象和叶子对象,这样对于组件对象和叶子对象都只要进行统一的操作;
    4. 统一叶子对象和组合对象,体现了开闭原则和里氏替换原则,用父类封装不变的部分、用子类实现扩展;

    image.png

    image.png

  2. 特点:

    1. 通常,组合模型会组合出树形结构;
    2. 对象本身的递归;
    3. 没有层次限制;
    4. 通常会在组件上为对某些子对象没有意义的方法提供默认的实现;
    5. 要考虑索引;
  3. 安全性和透明性:

    1. 安全性:客户调用组件不会误操作,能访问的方法都是被支持的功能;
    2. 透明性:客户不用区分具体的类型;

    image.png

  4. 子组件对父组件的引用

    1. 双向,用于删除、修改类别等场景;
  5. 环状引用

    1. 需要避免;
    2. 可以在组件中记录从根节点开始的路径;
    3. 可以在每次插入子节点时查找;
  6. 优点

    1. 构成一个统一的组合对象的类层次结构;
    2. 统一了组合对象和叶子对象;
    3. 简化了客户端调用;
    4. 更容易扩展;
  7. 缺点

    1. 难以限制组合中的组件类型
  8. 使用场景

    1. 表示对象的部分———整体层次结构;
    2. 统一地使用组合结构中的所有对象;

模板方法(template method)模式:行为型模式

  1. 模式

    1. 定义一个操作中算法的骨架,将重复或相似的代码抽离为公共的功能;
    2. 不同的步骤延迟到子类去实现;
    3. 功能:固定算法骨架,让具体的算法实现可扩展;
    4. 可以控制子类扩展;
    5. 既要约束子类的行为,又要为子类提供公共功能,所以采用了抽象类;
    6. 模板方法里,作为父类的模板会在需要时,调用子类相应的方法;
    7. 固定算法骨架;

    image.png

  2. 模板的写法:

    1. 通常在模板里包含以下操作类型:
      1. 模板方法:定义算法骨架的方法;
      2. 具体的操作:在模板中直接实现某些固定步骤的方法;
      3. 具体的 AbstractClass 操作:辅助的公共功能;
      4. 原语操作:模板中定义的抽象操作,需要子类来真正实现;
      5. 钩子方法:模板中定义,并提供默认实现的操作,子类可以有选择地覆盖这些方法。这通常被视为可扩展的点;
      6. factory method:如果需要获得对象实例,可以采用工厂方法,把具体的构建对象的实现延迟到子类中;
  3. 回调实现模板模式

    1. 除了继承外,也可以用回调实现模板模式,回调在接口中定义的方法,调用到具体的实现类中的方法,回调方法作为接口方法的参数传入;

    image.png

  4. 优点

    1. 实现代码复用,把公共部分放在模板里、实现了代码的复用;
  5. 缺点

    1. 算法骨架不容易升级;
  6. 使用场景

    1. 需要固定定义算法骨架,实现一个算法不变的部分、并把可变的行为留给子类来实现;
    2. 各个子类中具有公共部分,抽离可避免代码重复;
    3. 控制子类扩展;

策略(strategy)模式:行为型模式

  1. 模式

    1. 策略可以独立于调用的对象变化;
    2. 一个上下文对象负责持有算法,但不负责决定具体选用哪个算法,客户选择算法、设置到上下文对象中,并通知上下文对象执行功能,上下文对象则转调具体的算法;
    3. 功能:把具体的算法实现从具体的业务处理中独立出来;
    4. 客户端(选用具体的算法实现子类)或者上下文(把 context 当作参数传给 strategy)来选择具体的策略算法,策略可以使用接口或抽象类实现;
    5. 一系列算法具有公共的接口,来约束要实现的功能;
    6. 分离算法,选择实现;体现了开闭原则和里氏替换原则,已有的实现不需要修改就能做扩展、一系列算法可以互相替换

    image.png

    image.png

    image.png

  2. 特点

    1. 重点在于如何组织、调用算法,从而让程序结构更灵活,具有更好的维护性和扩展性;
    2. if-else 就是平等的功能结构,多个 if-else 可以考虑策略模式;
    3. 算法具有平等性:策略算法是相同行为的不同实现;
    4. 客户端或者上下文来选择具体的策略算法;
    5. 运行时,策略是唯一的;
    6. 扩展灵活;
  3. context 和 strategy 的关系

    1. 上下文封装着具体策略对象进行算法运算时所需要的数据;
    2. 算法实现对象可以回调上下文的方法实现一定的功能,这种情况下,上下文变相充当了策略算法实现的公共接口;
  4. 策略的拓展

    image.png

  5. 优点

    1. 定义一系列算法,可以互相替换,这一系列算法会有公共的接口;
    2. 避免多重条件(if-else)语句;
    3. 更好的扩展性,只要增加新的策略实现类,然后再使用的地方选择新策略;
  6. 缺点

    1. 客户必须了解每种策略的不同;
    2. 增加列对象数目;
    3. 扁平的算法结构;
  7. 使用场景

    1. 出现许多相关的类,仅仅是行为有差别,策略模式可实现算法动态切换;
    2. 一个算法有不同实现;
    3. 封装算法,避免暴露与算法相关的数据结构;
    4. 类中定义了很多行为,并通过多个 if-else 来选择行为;