Typescript玩转设计模式 之 对象行为型模式(下)

做者简介 joey 蚂蚁金服·数据体验技术团队html

本文是typescript设计模式系列文章的最后一篇,介绍了最后5个对象行为型的设计模式~java

  • 观察者模式
  • 状态模式
  • 策略模式
  • 模板模式
  • 访问者模式

Observer(观察者)

意图

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

结构

观察者模式包含如下角色:github

  • Subject(目标):目标又称为主题,它是指被观察的对象。在目标中定义了一个观察者集合,一个观察目标能够接受任意数量的观察者来观察,它提供一系列方法来增长和删除观察者对象,同时它定义了通知方法notify()。目标类能够是接口,也能够是抽象类或具体类。
  • ConcreteSubject(具体目标):具体目标是目标类的子类,一般它包含有常常发生改变的数据,当它的状态发生改变时,向它的各个观察者发出通知;同时它还实现了在目标类中定义的抽象业务逻辑方法(若是有的话)。若是无须扩展目标类,则具体目标类能够省略。
  • Observer(观察者):观察者将对观察目标的改变作出反应,观察者通常定义为接口,该接口声明了更新数据的方法update(),所以又称为抽象观察者。
  • ConcreteObserver(具体观察者):在具体观察者中维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态须要和具体目标的状态保持一致;它实现了在抽象观察者Observer中定义的update()方法。一般在实现时,能够调用具体目标类的attach()方法将本身添加到目标类的集合中或经过detach()方法将本身从目标类的集合中删除。

示例

推模型

目标向观察者发送关于改变的“详细信息”,而无论它们须要与否。由目标维护观察者。算法

// 场景:顾客点菜后,服务员记下顾客的信息,菜作好后广播通知顾客领取

  // 观察者基类
  class Observer {
    take(msg: string): void {}
  }

  // 目标基类
  class Subject {
    set: Set<Observer> = new Set();
    // 注册回调
    add(observer: Observer): void {
      this.set.add(observer);
    }
    // 注销回调
    remove(observer: Observer): void {
      this.set.delete(observer);
    }
    // 触发全部已注册的回调
    notify(msg: string): void {
      this.set.forEach(observer => {
        observer.take(msg);
      });
    }
  }

  // 具体目标,服务员类
  class Waiter extends Subject {
    // 菜作完后通知全部注册了的顾客
    ready(): void {
      this.notify('ready');
    }
  }

  // 具体观察者,顾客类
  class Client extends Observer {
    name: string;
    // 初始化时将自身注册到目标,以便接收通知
    constructor(name: string, waiter: Waiter) {
      super();
      this.name = name;
      waiter.add(this);
    }
    take(msg: string) {
      console.log(`顾客 ${this.name} 收到了消息显示状态是<${msg}>, 到吧台领取了菜`);
    }
  }

  function observerPushDemo() {
    const waiter = new Waiter();
    // 顾客点菜后,等待服务员通知
    const bob = new Client('Bob', waiter);
    const mick = new Client('Mick', waiter);
    // 菜准备好后,服务员广播通知顾客能够到吧台领取了
    waiter.ready();
  }
复制代码

拉模型

目标除了“最小通知”外什么也不送出,而在此以后由观察者显式地向目标询问细节。观察者里维护了目标对象。typescript

// 场景:顾客点菜后,收到通知从服务员处询问详细信息

  // 观察者基类
  class Observer {
    take(subject: Subject): void {}
  }

  // 目标基类
  class Subject {
    set: Set<Observer> = new Set();
    // 注册回调
    add(observer: Observer): void {
      this.set.add(observer);
    }
    // 注销回调
    remove(observer: Observer): void {
      this.set.delete(observer);
    }
    // 触发全部已注册的回调
    notify(): void {
      this.set.forEach(observer => {
        observer.take(this);
      });
    }
  }

  // 具体目标,服务员类
  class Waiter extends Subject {
    status = 'doing';
    // 与推模式的区别是,只发送通知,不发送详细数据
    ready(): void {
      this.status = 'ready';
      this.notify();
    }
    // 提供访问详细数据接口,让观察者访问详细数据
    getStatus(): string {
      return this.status;
    }
  }

  // 具体观察者,顾客类
  class Client extends Observer {
    name: string;
    // 初始化时将自身注册到目标,以便接收通知
    constructor(name: string, waiter: Waiter) {
      super();
      this.name = name;
      waiter.add(this);
    }
    // 与推模式的区别是,收到通知后,没有数据传入,须要从目标里读取
    take(waiter: Waiter) {
      const msg = waiter.getStatus();
      console.log(`顾客 ${this.name} 收到通知,询问服务员后发现状态是 <${msg}> 后领取了菜`);
    }
  }

  function observerPushDemo() {
    const waiter = new Waiter();
    // 顾客点菜
    const bob = new Client('Bob', waiter);
    const mick = new Client('Mick', waiter);
    // 菜准备完后,服务员通知了下全部顾客状态改变了,但没有发送内容出去,须要顾客再询问一下服务员才知道最新状态
    waiter.ready();
  }
复制代码

适用场景

  • 当一个抽象模型有两个方面,其中一个方面依赖于另外一方面。将这二者封装在独立的对象中以使它们能够各自独立地改变和复用;
  • 当一个对象的改变须要同时改变其余对象,而不知道具体有多少对象有待改变;
  • 当一个对象必须通知其余对象,而它又不能假定其余对象是谁。换言之,你不但愿这些对象是紧密耦合的;

优势

  • 目标和观察者间的抽象耦合。一个目标所知道的仅仅是它有一系列观察者,每一个都符合抽象的Observer类的简单接口。目标不须要知道任何一个观察者属于哪个具体的类。
  • 支持广播通讯。目标发现的通知不须要指定它的接收者。目标对象并不关心有多少观察者对象对本身感兴趣,惟一的职责就是通知已注册的各观察者。

缺点

  • 意外的更新。由于一个观察者并不知道其余观察者的存在,它可能对改变目标的最终代价一无所知。在目标上一个看似无害的操做可能会引发一系列对观察者以及依赖于这些观察者的那些对象的更新。由此引起的问题经常难以追踪。

相关模式

  • Mediator:经过封装复杂的更新语义,ChangeManager充当目标和观察者之间的中介者。
  • Singleton:ChangeManager可以使用单例模式来保证它是惟一的而且是可全局访问的。

State(状态)

意图

容许一个对象在其内部状态改变时改变它的行为。对象看起来彷佛修改了它的类。设计模式

结构

状态模式包含如下角色:bash

  • Context(环境类):环境类又称为上下文类,它是拥有多种状态的对象。因为环境类的状态存在多样性且在不一样状态下对象的行为有所不一样,所以将状态独立出去造成单独的状态类。在环境类中维护一个抽象状态类State的实例,这个实例定义当前状态,在具体实现时,它是一个State子类的对象。
  • State(抽象状态类):它用于定义一个接口以封装与环境类的一个特定状态相关的行为,在抽象状态类中声明了各类不一样状态对应的方法,而在其子类中实现类这些方法,因为不一样状态下对象的行为可能不一样,所以在不一样子类中方法的实现可能存在不一样,相同的方法能够写在抽象状态类中。
  • ConcreteState(具体状态类):它是抽象状态类的子类,每个子类实现一个与环境类的一个状态相关的行为,每个具体状态类对应环境的一个具体状态,不一样的具体状态类其行为有所不一样。

示例

// 帐户有几种状态:正常,透支,受限

  // 帐户类,表明状态模式中的环境
  class Account {
    private name: string;
    private state: State;
    // 余额
    private balance = 0;
    // 初始时为正常状态
    constructor(name: string) {
      this.name = name;
      this.state = new NormalState(this);
      console.log(`用户 ${this.name} 开户,余额为 ${this.balance}`);
      console.log('--------');
    }
    getBalance(): number {
      return this.balance;
    }
    setBalance(balance: number) {
      this.balance = balance;
    }
    setState(state: State) {
      this.state = state;
    }
    // 存款
    deposit(amount: number) {
      this.state.deposit(amount);
      console.log(`存款 ${amount}`);
      console.log(`余额为 ${this.balance}`);
      console.log(`帐户状态为 ${this.state.getName()}`);
      console.log('--------');
    }
    // 取款
    withdraw(amount: number) {
      this.state.withdraw(amount);
      console.log(`取款 ${amount}`);
      console.log(`余额为 ${this.balance}`);
      console.log(`帐户状态为 ${this.state.getName()}`);
      console.log('--------');
    }
    // 结算利息
    computeInterest() {
      this.state.computeInterest();
    }
  }

  // 状态抽象类
  abstract class State {
    private name: string;
    protected acc: Account;
    constructor(name: string) {
      this.name = name;
    }
    getName() {
      return this.name;
    }
    abstract deposit(amount: number);  
    abstract withdraw(amount: number);  
    abstract computeInterest();  
    abstract stateCheck();
  }

  // 正常状态类
  class NormalState extends State {
    acc: Account;
    constructor(acc: Account) {
      super('正常');
      this.acc = acc;
    }
    deposit(amount: number) {
      this.acc.setBalance(this.acc.getBalance() + amount);
      this.stateCheck();
    }
    withdraw(amount: number) {
      this.acc.setBalance(this.acc.getBalance() - amount);  
      this.stateCheck();
    }
    computeInterest() {
      console.log('正常状态,无须支付利息');
    }
    // 状态转换
    stateCheck() {
      if (this.acc.getBalance() > -2000 && this.acc.getBalance() <= 0) {  
          this.acc.setState(new OverdraftState(this.acc));  
      } else if (this.acc.getBalance() == -2000) {  
          this.acc.setState(new RestrictedState(this.acc));  
      } else if (this.acc.getBalance() < -2000) {  
          console.log('操做受限');  
      }
    }
  }

  // 透支状态
  class OverdraftState extends State {
    acc: Account;
    constructor(acc: Account) {
      super('透支');
      this.acc = acc;
    }
    deposit(amount: number) {
      this.acc.setBalance(this.acc.getBalance() + amount);
      this.stateCheck();
    }
    withdraw(amount: number) {
      this.acc.setBalance(this.acc.getBalance() - amount);  
      this.stateCheck();
    }
    computeInterest() {
      console.log('计算利息');
    }
    // 状态转换
    stateCheck() {
      if (this.acc.getBalance() > 0) {
        this.acc.setState(new NormalState(this.acc));
      } else if (this.acc.getBalance() == -2000) {
        this.acc.setState(new RestrictedState(this.acc));
      } else if (this.acc.getBalance() < -2000) {
        console.log('操做受限');
      }
    }
  }

  // 受限状态
  class RestrictedState extends State {
    acc: Account;
    constructor(acc: Account) {
      super('受限');
      this.acc = acc;
    }
    deposit(amount: number) {
      this.acc.setBalance(this.acc.getBalance() + amount);
      this.stateCheck();
    }
    withdraw(ammount: number) {
      console.log('帐号受限,取款失败');
    }
    computeInterest() {
      console.log('计算利息');
    }
    // 状态转换
    stateCheck() {
      if (this.acc.getBalance() > 0) {  
        this.acc.setState(new NormalState(this.acc));  
      } else if (this.acc.getBalance() > -2000) {  
        this.acc.setState(new OverdraftState(this.acc));  
      }
    }
  }

  function stateDemo() {
    const acc = new Account('Bob');
    acc.deposit(1000);  
    acc.withdraw(2000);  
    acc.deposit(3000);  
    acc.withdraw(4000);  
    acc.withdraw(1000);  
    acc.computeInterest(); 
  }
复制代码

适用场景

  • 一个对象的行为取决于它的状态,而且它必须在运行时刻根据状态改变它的行为;
  • 一个操做中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态。这个状态一般用一个或多个枚举常量表示。有多个操做包含这一相同的条件结构。状态模式将每个条件分支放入一个独立的类中。这使得你能够根据对象自身的状况将对象的状态做为一个对象,这一对象能够不依赖于其余对象而独立变化。

优势

  • 封装了状态的转换规则,在状态模式中能够将状态的转换代码封装在环境类或者具体状态类中,能够对状态转换代码进行集中管理,而不是分散在一个个业务方法中。
  • 将全部与某个状态有关的行为放到一个类中,只须要注入一个不一样的状态对象便可使环境对象拥有不一样的行为。
  • 容许状态转换逻辑与状态对象合成一体,而不是提供一个巨大的条件语句块,状态模式可让咱们避免使用庞大的条件语句来将业务方法和状态转换代码交织在一块儿。
  • 可让多个环境对象共享一个状态对象,从而减小系统中对象的个数。

缺点

  • 状态模式的使用必然会增长系统中类和对象的个数,致使系统运行开销增大。
  • 状态模式的结构与实现都较为复杂,若是使用不当将致使程序结构和代码的混乱,增长系统设计的难度。
  • 状态模式对“开闭原则”的支持并不太好,增长新的状态类须要修改那些负责状态转换的源代码,不然没法转换到新增状态;并且修改某个状态类的行为也需修改对应类的源代码。

相关模式

  • 享元模式解释了什么时候以及怎样共享状态对象;
  • 状态对象一般是单例;

Strategy(策略模式)

意图

定义一系列的算法,把它们一个个封装起来,而且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。数据结构

结构

策略模式包含如下角色:框架

  • Context(环境类):环境类是使用算法的角色,它在解决某个问题(即实现某个方法)时能够采用多种策略。在环境类中维持一个对抽象策略类的引用实例,用于定义所采用的策略。
  • Strategy(抽象策略类):它为所支持的算法声明了抽象方法,是全部策略类的父类,它能够是抽象类或具体类,也能够是接口。环境类经过抽象策略类中声明的方法在运行时调用具体策略类中实现的算法。
  • ConcreteStrategy(具体策略类):它实现了在抽象策略类中声明的算法,在运行时,具体策略类将覆盖在环境类中定义的抽象策略类对象,使用一种具体的算法实现某个业务处理。

示例

// 火车票类:环境类
class TrainTicket {
  private price: number;
  private discount: Discount;
  constructor(price: number) {
    this.price = price;
  }
  setDiscount(discount: Discount) {
    this.discount = discount;
  }
  getPrice(): number {
    return this.discount.calculate(this.price);
  }
}

// 折扣接口
interface Discount {
  calculate(price: number): number;
}

// 学生票折扣
class StudentDiscount implements Discount {
  calculate(price: number): number {
    console.log('学生票打7折');
    return price * 0.7;
  }
}

// 儿童票折扣
class ChildDiscount implements Discount {
  calculate(price: number): number {
    console.log('儿童票打5折');
    return price * 0.5;
  }
}

// 军人票折扣
class SoldierDiscount implements Discount {
  calculate(price: number): number {
    console.log('军人免票');
    return 0;
  }
}

function strategyDemo() {
  const ticket: TrainTicket = new TrainTicket(100);

  // 从环境中获取到身份信息,而后根据身份信息获取折扣策略
  const discount: Discount = getIdentityDiscount();
  // 注入折扣策略对象
  ticket.setDiscount(discount);
  // 根据策略对象获取票价
  console.log(ticket.getPrice());
}
复制代码

适用场景

  • 许多相关的类仅仅是行为有异。“策略”提供了一种用多个行为中的一个行为来配置一个类的方法。
  • 须要使用一个算法的不一样变体。
  • 算法使用客户不该该知道的数据。可以使用策略模式以免暴露复杂的、与算法有关的数据结构。
  • 一个类定义了多种行为,而且这些行为在这个类的操做中以多个条件语句的形式出现。将相关的条件分支移入它们各自的策略类中以代替这些条件语句。

优势

  • 提供了对“开闭原则”的完美支持,用户能够在不修改原有系统的基础上选择算法或行为,也能够灵活地增长新的算法或行为。
  • 提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族,恰当使用继承能够把公共的代码移到抽象策略类中,从而避免重复的代码。
  • 提供了一种能够替换继承关系的办法。若是不使用策略模式,那么使用算法的环境类就可能会有一些子类,每个子类提供一种不一样的算法。可是,这样一来算法的使用就和算法自己混在一块儿,不符合“单一职责原则”,决定使用哪种算法的逻辑和该算法自己混合在一块儿,从而不可能再独立演化;并且使用继承没法实现算法或行为在程序运行时的动态切换。
  • 使用策略模式能够避免多重条件选择语句。多重条件选择语句不易维护,它把采起哪种算法或行为的逻辑与算法或行为自己的实现逻辑混合在一块儿,将它们所有硬编码在一个庞大的多重条件选择语句中,比直接继承环境类的办法还要原始和落后。
  • 提供了一种算法的复用机制,因为将算法单独提取出来封装在策略类中,所以不一样的环境类能够方便地复用这些策略类。

缺点

  • 客户端必须知道全部的策略类,并自行决定使用哪个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法。换言之,策略模式只适用于客户端知道全部的算法或行为的状况。
  • 策略模式将形成系统产生不少具体策略类,任何细小的变化都将致使系统要增长一个新的具体策略类。
  • 没法同时在客户端使用多个策略类,也就是说,在使用策略模式时,客户端每次只能使用一个策略类,不支持使用一个策略类完成部分功能后再使用另外一个策略类来完成剩余功能的状况。

相关模式

  • 享元: 策略对象常常是很好的轻量级对象。

Template Method(模板方法)

意图

定义一个操做中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类能够不改变一个算法的结构便可重定义该算法的某些特定步骤。

结构

模板方法包含如下角色:

  • AbstractClass(抽象类):在抽象类中定义了一系列基本操做(PrimitiveOperations),这些基本操做能够是具体的,也能够是抽象的,每个基本操做对应算法的一个步骤,在其子类中能够重定义或实现这些步骤。同时,在抽象类中实现了一个模板方法(Template Method),用于定义一个算法的框架,模板方法不只能够调用在抽象类中实现的基本方法,也能够调用在抽象类的子类中实现的基本方法,还能够调用其余对象中的方法。
  • ConcreteClass(具体子类):它是抽象类的子类,用于实如今父类中声明的抽象基本操做以完成子类特定算法的步骤,也能够覆盖在父类中已经实现的具体基本操做。

示例

模板方法是基于继承的一种模式。

下面是一个组件渲染的例子,模拟React组件渲染流程。

// 组件基类
class Component {
  // 模板方法,把组件渲染的流程定义好
  setup() {
    this.componentWillMount();
    this.doRender();
    this.componentDidMount();
  }
  private doRender() {
    // 作实际的渲染工做
  }
  componentWillMount() {}
  componentDidMount() {}
}

class ComponentA extends Component {
  componentWillMount() {
    console.log('A组件即将被渲染');
  }
  componentDidMount() {
    console.log('A组件渲染完成');
  }
}

class ComponentB extends Component {
  componentWillMount() {
    console.log('B组件即将被渲染');
  }
  componentDidMount() {
    console.log('B组件渲染完成');
  }
}

// 渲染A和B组件,生命周期的流程都是相同的,已经在模板方法里定义好了的
function templateMethodDemo() {
  const compA = new ComponentA();
  compA.setup();

  const compB = new ComponentB();
  compB.setup();
}
复制代码

适用场景

  • 须要控制流程的逻辑顺序时。模板方法模式普遍应用于框架设计中,以确保经过父类来控制处理流程的逻辑顺序(如框架的初始化,测试流程的设置等)

优势

  • 在父类中形式化地定义一个算法,而由它的子类来实现细节的处理,在子类实现详细的处理算法时并不会改变算法中步骤的执行次序。
  • 模板方法模式是一种代码复用技术,它在类库设计中尤其重要,它提取了类库中的公共行为,将公共行为放在父类中,而经过其子类来实现不一样的行为,它鼓励咱们恰当使用继承来实现代码复用。
  • 可实现一种反向控制结构,经过子类覆盖父类的钩子方法来决定某一特定步骤是否须要执行。
  • 在模板方法模式中能够经过子类来覆盖父类的基本方法,不一样的子类能够提供基本方法的不一样实现,更换和增长新的子类很方便,符合单一职责原则和开闭原则。

缺点

  • 须要为每个基本方法的不一样实现提供一个子类,若是父类中可变的基本方法太多,将会致使类的个数增长,系统更加庞大,设计也更加抽象,此时,可结合桥接模式来进行设计。

相关模式

  • 工厂方法: 常被模板方法调用。
  • 策略模式:模板方法使用继承来改变算法的一部分。策略模式使用委托来改变整个算法。

访问者模式

意图

提供一个做用于某对象结构中的各元素的操做表示,它使咱们能够在不改变各元素的类的前提下定义做用于这些元素的新操做。

结构

访问者模式包含如下角色:

  • Vistor(抽象访问者):抽象访问者为对象结构中每个具体元素类ConcreteElement声明一个访问操做,从这个操做的名称或参数类型能够清楚知道须要访问的具体元素的类型,具体访问者须要实现这些操做方法,定义对这些元素的访问操做。
  • ConcreteVisitor(具体访问者):具体访问者实现了每一个由抽象访问者声明的操做,每个操做用于访问对象结构中一种类型的元素。
  • Element(抽象元素):抽象元素通常是抽象类或者接口,它定义一个accept()方法,该方法一般以一个抽象访问者做为参数。【稍后将介绍为何要这样设计。】
  • ConcreteElement(具体元素):具体元素实现了accept()方法,在accept()方法中调用访问者的访问方法以便完成对一个元素的操做。
  • ObjectStructure(对象结构):对象结构是一个元素的集合,它用于存放元素对象,而且提供了遍历其内部元素的方法。它能够结合组合模式来实现,也能够是一个简单的集合对象,如一个List对象或一个Set对象。

示例

一个公司有两种员工,正式工和临时工,他们有不一样的工时和薪酬结算方法。

// 员工接口
  interface Employee {
    accept(handler: Department): void;
  }

  // 全职员工类
  class FulltimeEmployee implements Employee {
    private name = '';
    // 全职员工按周薪计算薪酬
    private weeklyWage = 0;
    // 工做时长
    private workTime = 0;
    constructor(name: string, weeklyWage: number, workTime: number) {
      this.name = name;
      this.weeklyWage = weeklyWage;
      this.workTime = workTime;
    }
    getName(): string {
      return this.name;
    }
    getWeeklyWage(): number {
      return this.weeklyWage;
    }
    getWorkTime(): number {
      return this.workTime;
    }
    // 实现接口,调用访问者处理全职员工的方法
    accept(handler: Department) {
      handler.visitFulltime(this);
    }
  }

  // 临时员工类
  class ParttimeEmployee implements Employee {
    private name = '';
    // 临时员工按时薪计算薪酬
    private hourWage = 0;
    // 工做时长
    private workTime = 0;
    constructor(name: string, hourWage: number, workTime: number) {
      this.name = name;
      this.hourWage = hourWage;
      this.workTime = workTime;
    }
    getName(): string {
      return this.name;
    }
    getHourWage(): number {
      return this.hourWage;
    }
    getWorkTime(): number {
      return this.workTime;
    }
    // 实现接口,调用访问者处理临时工的方法
    accept(handler: Department) {
      handler.visitParttime(this);
    }
  }

  // 部门接口
  interface Department {
    visitFulltime(employee: FulltimeEmployee): void;
    visitParttime(employee: ParttimeEmployee): void;
  }

  // 具体访问者——财务部,结算薪酬实现部门接口
  class FADepartment implements Department {
    // 全职员工薪酬计算方式
    visitFulltime(employee: FulltimeEmployee) {
      const name: string = employee.getName();
      let workTime: number = employee.getWorkTime();
      let weekWage: number = employee.getWeeklyWage();
      const WEEK_WORK_TIME = 40;
      if (workTime > WEEK_WORK_TIME) {
        // 计算加班工资
        const OVER_TIME_WAGE = 100;
        weekWage = weekWage + (workTime - WEEK_WORK_TIME) * OVER_TIME_WAGE;
      } else if (workTime < WEEK_WORK_TIME) {
        if (workTime < 0) {
          workTime = 0;
        }
        // 扣款
        const CUT_PAYMENT = 80;
        weekWage = weekWage - (WEEK_WORK_TIME - workTime) * CUT_PAYMENT;
      }
      console.log(`正式员工 ${name} 实际工资为:${weekWage}`);
    }
    // 临时工薪酬计算方式
    visitParttime(employee: ParttimeEmployee) {
      const name = employee.getName();
      const hourWage = employee.getHourWage();
      const workTime = employee.getWorkTime();
      console.log(`临时工 ${name} 实际工资为:${hourWage * workTime}`);
    }
  }

  // 具体访问者——人力资源部,结算工做时间,实现部门接口
  class HRDepartment implements Department {
    // 全职员工工做时间报告
    visitFulltime(employee: FulltimeEmployee) {
      const name: string = employee.getName();
      let workTime: number = employee.getWorkTime();
      // 实际工做时间报告
      let report = `正式员工 ${name} 实际工做时间为 ${workTime} 小时`;
      const WEEK_WORK_TIME = 40;
      if (workTime > WEEK_WORK_TIME) {
        // 加班时间报告
        report = `${report},加班 ${WEEK_WORK_TIME - workTime} 小时`;
      } else if (workTime < WEEK_WORK_TIME) {
        if (workTime < 0) {
          workTime = 0;
        }
        // 请假时间报告
        report = `${report},请假 ${WEEK_WORK_TIME - workTime} 小时`;
      }
      console.log(report);
    }
    // 临时工工做时间报告
    visitParttime(employee: ParttimeEmployee) {
      const name: string = employee.getName();
      const workTime: number = employee.getWorkTime();
      console.log(`临时工 ${name} 实际工做时间为 ${workTime} 小时`);
    }
  }

  // 员工集合类
  class EmployeeList {
    list: Array<Employee> = [];
    add(employee: Employee) {
      this.list.push(employee);
    }
    // 遍历员工集合中的每个对象
    accept(handler: Department) {
      this.list.forEach((employee: Employee) => {
        employee.accept(handler);
      });
    }
  }

  function visitorDemo() {
    const list: EmployeeList = new EmployeeList();
    const full1 = new FulltimeEmployee('Bob', 3000, 45);
    const full2 = new FulltimeEmployee('Mikel', 2000, 35);
    const full3 = new FulltimeEmployee('Joe', 4000, 40);
    const part1 = new ParttimeEmployee('Lili', 80, 20);
    const part2 = new ParttimeEmployee('Lucy', 60, 15);

    list.add(full1);
    list.add(full2);
    list.add(full3);
    list.add(part1);
    list.add(part2);

    // 财务部计算薪酬
    const faHandler = new FADepartment();
    list.accept(faHandler);

    // 人力资源部出工做报告
    const hrHandler = new HRDepartment();
    list.accept(hrHandler);
  }
复制代码

适用场景

  • 一个对象结构包含多个类型的对象,但愿对这些对象实施一些依赖其具体类型的操做。在访问者中针对每一种具体的类型都提供了一个访问操做,不一样类型的对象能够有不一样的访问操做。
  • 须要对一个对象结构中的对象进行不少不一样的而且不相关的操做,而须要避免让这些操做“污染”这些对象的类,也不但愿在增长新操做时修改这些类。访问者模式使得咱们能够将相关的访问操做集中起来定义在访问者类中,对象结构能够被多个不一样的访问者类所使用,将对象自己与对象的访问操做分离。
  • 对象结构中对象对应的类不多改变,但常常须要在此对象结构上定义新的操做。

优势

  • 增长新的访问操做很方便。使用访问者模式,增长新的访问操做就意味着增长一个新的具体访问者类,实现简单,无须修改源代码,符合“开闭原则”。
  • 将有关元素对象的访问行为集中到一个访问者对象中,而不是分散在一个个的元素类中。类的职责更加清晰,有利于对象结构中元素对象的复用,相同的对象结构能够供多个不一样的访问者访问。
  • 让用户可以在不修改现有元素类层次结构的状况下,定义做用于该层次结构的操做。

缺点

  • 增长新的元素类很困难。在访问者模式中,每增长一个新的元素类都意味着要在抽象访问者角色中增长一个新的抽象操做,并在每个具体访问者类中增长相应的具体操做,这违背了“开闭原则”的要求。
  • 破坏封装。访问者模式要求访问者对象访问并调用每个元素对象的操做,这意味着元素对象有时候必须暴露一些本身的内部操做和内部状态,不然没法供访问者访问。

相关模式

  • 组合模式:访问者能够用于对一个由组合模式定义的对象结构进行操做;

参考文档

本文介绍了最后5种对象行为型模式,多谢你们对于系列文章的支持~对团队感兴趣的同窗能够关注专栏或者发送简历至'tao.qit####alibaba-inc.com'.replace('####', '@'),欢迎有志之士加入~

原文地址:github.com/ProtoTeam/b…

相关文章
相关标签/搜索