Skip to content

行为变化模式

概念

定义

  • 在组件构建过程中,组件行为的变化经常导致组件本身剧烈的变化。
  • “行为变化”模式 「将组件的行为与组件本身进行解耦」,从而支持组件行为的变化,实现两者之间的松耦合。

命令模式(Command)

动机

  • 在软件构建过程中,“行为请求者”和“行为实现者”通常呈现一种“紧耦合”。但在某些场合——比如需要对行为进行“记录、撤销、重置、事务”等处理,这种无法抵御变化的紧耦合是不合适的。
  • 在这种情况下,如何将“行为请求者”和“行为实现者”解耦?将一组行为抽象为对象,可以实现二者之间的紧耦合。

模式定义

  • 将一个请求(行为)封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及可撤销的操作。
  • 方便将命令(行为)对象进行储存、传递、调用、增加与管理。

UML 类图

x

角色

  • 抽象命令类(Command)角色:声明执行命令的接口,拥有执行命令的抽象方法 execute()。
  • 具体命令类(Concrete Command)角色:是抽象命令类的具体实现类,它拥有接收者对象,并通过调用接收者的功能来完成命令要执行的操作。
  • 实现者/接收者(Receiver)角色:执行命令功能的相关操作,是具体命令对象业务的真正实现者。
  • 调用者/请求者(Invoker)角色:是请求的发送者,它通常拥有很多的命令对象,并通过访问命令对象来执行相关请求,它不直接访问接收者。

代码

// 调用者
class Invoker {
  private command: Command;
  public constructor(command: Command) {
    this.command = command;
  }
  public setCommand(command: Command): void {
    this.command = command;
  }
  public call(): void {
    console.log('调用者执行命令command...');
    this.command.execute();
  }
}

// 抽象命令
interface Command {
  execute(): void;
}

// 具体命令
class ConcreteCommand implements Command {
  private receiver: Receiver;
  constructor() {
    this.receiver = new Receiver();
  }
  public execute(): void {
    this.receiver.action();
  }
}

// 接收者
class Receiver {
  public action(): void {
    console.log('接收者的action()方法被调用...');
  }
}

export default class CommandPattern {
  public static main(args: string[]): void {
    const cmd: Command = new ConcreteCommand();
    const ir: Invoker = new Invoker(cmd);
    console.log('客户访问调用者的call()方法...');
    ir.call();
  }
}

要点总结

  • Command 模式的根本目的在于将“行为请求者”和“行为实现者”解耦;在面向对象语言中,常见的实现手段是 “将行为抽象为对象”。
  • 实现 Command 接口的具体命令对象ConcreteCommand有时候根据需要可能会保存一些额外的状态信息。通过使用Composite模式,可以将多个命令封装为一个“复合命令” MacroCommand。
  • Command模式和c++中函数对象有些类似。但二者定义行为接口的规范有些区别:Command以面向对象中的“接口-实现”来定义行为接口规范,更严格,但有性能损失;c++函数对象以函数签名来定义行为接口规范,更灵活,性能更高。
  • 场景:看电视时,一按遥控器就能完成频道的切换,将换台请求和换台处理完全解耦。电视机遥控器(命令发送者)通过按钮(具体命令)来遥控电视机(命令接收者)

访问者模式(Visitor)

动机

  • 在软件构建的过程中,由于需求的变化,某些类层次结构中,常常需要增加新的的行为(方法);如果「直接在基类中做这样的更改」,将会给子类带来很繁重的变更负担,甚至破坏原有设计。
  • 如何在不变更类层次结构的前提下,在运行时根据需要透明地为类层次结构上的各个类动态添加新的操作,从而避免上述问题?

模式定义

  • Visitor 表示一个作用于某对象结构中的各元素的操作。使得可以在不改变(稳定)各元素的类的前提下,定义(扩展)作用于这些元素的新操作(变化)。
  • 将「对数据的操作」与「数据结构」进行分离;行为类模式中最复杂的一种模式。

UML 类图

x

角色

  • 抽象访问者(Visitor)角色:定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作 visit() ,该操作中的参数类型标识了被访问的具体元素。
  • 具体访问者(ConcreteVisitor)角色:实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么。
  • 抽象元素(Element)角色:声明一个包含接受操作 accept() 的接口,被接受的访问者对象作为 accept() 方法的参数。
  • 具体元素(ConcreteElement)角色:实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作。
  • 对象结构(Object Structure)角色:是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等聚合类实现。

代码

// 抽象访问者
interface Visitor {
  visitX(element: ConcreteElementX): void;

  visitY(element: ConcreteElementY): void;
}

// 具体访问者A类
class ConcreteVisitorA implements Visitor {
  public visitX(element: ConcreteElementX): void {
    console.log('具体访问者A访问-->' + element.operationX());
  }

  public visitY(element: ConcreteElementY): void {
    console.log('具体访问者A访问-->' + element.operationY());
  }
}

// 具体访问者B类
class ConcreteVisitorB implements Visitor {
  public visitX(element: ConcreteElementX): void {
    console.log('具体访问者B访问-->' + element.operationX());
  }

  public visitY(element: ConcreteElementY): void {
    console.log('具体访问者B访问-->' + element.operationY());
  }
}

// 抽象元素类
interface Element {
  accept(visitor: Visitor): void;
}

// 具体元素X类
class ConcreteElementX implements Element {
  public accept(visitor: Visitor): void {
    visitor.visitX(this);
  }

  public operationX(): string {
    return '具体元素X的操作。';
  }
}

// 具体元素Y类
class ConcreteElementY implements Element {
  public accept(visitor: Visitor): void {
    visitor.visitY(this);
  }

  public operationY(): string {
    return '具体元素Y的操作。';
  }
}

// 对象结构角色
class ObjectStructure {
  private list: Array<Element> = new Array<Element>();

  public accept(visitor: Visitor): void {
    const i: IterableIterator<Element> = this.list[Symbol.iterator]();

    let x = i.next();

    while (!x.done) {
      x.value.accept(visitor);
      x = i.next();
    }
  }

  public add(element: Element): void {
    this.list.push(element);
  }

  public remove(element: Element): void {
    const index = this.list.indexOf(element);

    if (index > -1) {
      this.list.splice(index, 1);
    }
  }
}

export default class VisitorPattern {
  public static main(args: string[]): void {
    const os: ObjectStructure = new ObjectStructure();
    os.add(new ConcreteElementX());
    os.add(new ConcreteElementY());

    let visitor: Visitor = new ConcreteVisitorA();
    os.accept(visitor);

    console.log('----------------');

    visitor = new ConcreteVisitorB();
    os.accept(visitor);
  }
}

要点总结

  • Visitor模式通过所谓双重分发(double dispatch)来实现在不更改(不添加新的操作--编译时)Element类层次结构的前提下,在运行时透明地为类层次结构上的各个类动态添加新的操作(支持变化)。
  • 所谓双重分发即 Visitor 模式中间包括了两个多态分发(注意其中的多态机制):第一个为accept方法的多态辨析,第二个为visitElementX方法的多态辨析。
  • Visitor模式的最大缺点在于扩展类层次结构(增添新的Element子类),会导致Visitor类的改变。因此Visitor模式适用于“Element类层次结构稳定,而其中的操作却经常面临频繁改动”。
  • 原则验证

    • 符合单一职责原则:访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。
    • 违反依赖倒置原则:访问者模式依赖了具体类,而没有依赖抽象类。