面向面试编程:设计模式——行为型模式(下)

178 阅读8分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第20天,点击查看活动详情

面试官:说说你了解的设计模式

Observer 观察者模式

定义

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

角色

  • 抽象目标角色(Subject):目标角色知道它的观察者,可以有任意多个观察者观察同一个目标。
  • 抽象观察者角色(Observer):为那些在目标发生改变时需要获得通知的对象定义一个更新接口。
  • 具体目标角色(Concrete Subject):将有关状态存入各个Concrete Observer 对象。
  • 具体观察者角色(Concrete Observer):存储有关状态,这些状态应与目标的状态保持一致。

优缺点

优点

  • 观察者和被观察者是抽象耦合的。
  • 建立一套触发机制。

缺点

  • 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
  • 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
  • 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

使用场景

  • 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
  • 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
  • 一个对象必须通知其他对象,而并不知道这些对象是谁。

注意事项

  • JAVA 中已经有了对观察者模式的支持类。
  • 避免循环引用。
  • 如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。

State 状态模式

定义

当一个对象内在状态改变时允许其改变行为,这个对象看起来像改变了其类。

角色

  • Context:环境类,也称为上下文,它定义了客户感兴趣的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理。
  • State:抽象状态类,定义一个接口,用以封装环境对象中的特定状态所对应的行为。
  • Concretestate:具体状态类,实现抽象状态所对应的行为。

优缺点

优点

  • 封装了转换规则。
  • 枚举可能的状态,在枚举状态之前需要确定状态种类。
  • 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
  • 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。
  • 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。

缺点

  • 状态模式的使用必然会增加系统类和对象的个数。
  • 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
  • 状态模式对“开闭原则”的支持并不太好。
    • 增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态。
    • 修改某个状态类的行为也需修改对应类的源代码。

使用场景

  • 行为随状态改变而改变的的场景,例如权限设计,人员对象权限不同执行相同的行为其结果也会不同。
  • 条件、分支判断语句替代者,通过扩展子类实现条件判断处理。

Strategy 策略模式

定义

定义一系列算法,将每个算法封装到具有公共接口的一系列策略类中,从而使它们可以相互替换,并让算法可以在不影响到客户端的情况下发生变化。

作用

使得客户端可以根据外部条件选择不同策略来解决不同问题。

优缺点

优点

  • 策略类之间可以自由切换:由于策略类都实现同一个接口,所以使它们之间可以自由切换。
  • 易于扩展:增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码,符合“开闭原则”。
  • 避免使用多重条件选择语句(if else),充分体现面向对象设计思想。

缺点

  • 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
  • 策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。

使用场景

  • 一个系统需要动态地在几种算法中选择一种的情况。
  • 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
  • 如果一个对象有很多的行为,使用多重的if-else语句来实现,可以使用策略模式,把这些行为转移到相应的具体策略类里面,就可以避免使用难以维护的多重条件选择语句。
  • 不希望客户端知道复杂的、与算法相关的数据结构,在具体策略类中封装算法和相关的数据结构,提高算法的保密性与安全性。

Template Method 模板方法模式

定义

定义一个模板结构,将具体内容延迟到子类去实现。

作用

在不改变模板结构的前提下在子类中重新定义模板中的内容。

优缺点

优点

  • 提高代码复用性 :将相同部分的代码放在抽象的父类中。
  • 提高了拓展性 :将不同的代码放入不同的子类中,通过对子类的扩展增加新的行为。
  • 实现了反向控制 :通过一个父类调用其子类的操作,通过对子类的扩展增加新的行为,实现了反向控制 & 符合“开闭原则”。

缺点

引入了抽象类,每一个不同的实现都需要一个子类来实现,导致类的个数增加,从而增加了系统实现的复杂度。

使用场景

  • 一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现。
  • 各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。
  • 控制子类的扩展。

Visitor 访问者模式

定义

封装某些作用于某种数据结构中各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。

角色

  • 抽象访问者:抽象类或者接口,声明访问者可以访问哪些元素,具体到程序中就是visit方法中的参数定义哪些对象是可以被访问的。
  • 访问者:实现抽象访问者所声明的方法,它影响到访问者访问到一个类后该干什么,要做什么事情。
  • 抽象元素类:接口或者抽象类,声明接受哪一类访问者访问,程序上是通过accept方法中的参数来定义的。
  • 元素类:实现抽象元素类所声明的accept方法,通常都是visitor.visit(this),基本上已经形成一种定式了。
  • 结构对象:一个元素的容器,一般包含一个容纳多个不同类、不同接口的容器,如List、Set、Map等,在项目中一般很少抽象出这个角色。

优缺点

优点

  • 使得数据结构和作用于结构上的操作解耦,使得操作集合可以独立变化。
  • 添加新的操作或者说访问者会非常容易。
  • 将对各个元素的一组操作集中在一个访问者类当中。
  • 使得类层次结构不改变的情况下,可以针对各个层次做出不同的操作,而不影响类层次结构的完整性。
  • 可以跨越类层次结构,访问不同层次的元素类,做出相应的操作。

缺点

  • 增加新的元素会非常困难。
  • 实现起来比较复杂,会增加系统的复杂性。
  • 破坏封装:为了让访问者能获取到所关心的信息,元素类不得不暴露出一些内部的状态和结构。

使用场景

  • 数据结构稳定,作用于数据结构的操作经常变化的时候。
  • 当一个数据结构中,一些元素类需要负责与其不相关的操作的时候,为了将这些操作分离出去,以减少这些元素类的职责时,可以使用访问者模式。
  • 在对数据结构上的元素进行操作的时需要区分具体的类型,访问者模式可以针对不同的类型定义不同的操作,从而去除掉类型判断。