木灵鱼儿

木灵鱼儿

阅读:401

最后更新:2022/06/21/ 2:40:07

行为模式:访问者模式

简介

访问者模式:将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。

有一点点抽象,前一句话的意思是指在写好一个类之后,这个类就基本上不需要改动了(只要需求不改),这其实就是为了解决类结构不变但操作处理逻辑易变的问题,把对数据的操作都封装到访问者类中,我们只需要调用不同的访问者,而无需改变改变结构类,实现了。

简单来说就是将一些操作作为扩展分离出去, 这些扩展的功能可能现在没有,以后会有,我们无法知道以后的情况,于是会给每个类添加一个接口函数,函数接收一个访问者对象,用于对接扩展,使用的时候调用访问者对象的指定方法,传入自身this作为参数给他。

此时访问者可以通过this访问到类的方法,去做一些扩展操作。

我们看下角色:

  1. 抽象的访问者:定义了具体访问者的接口,并且作为元素的扩展接口参数类型依赖
  2. 具体的访问者:实现抽象访问者的接口,是具体的逻辑
  3. 抽象元素:声明一个扩展用的接口函数,比如accept(),它接受一个参数,参数类型为抽象的访问者
  4. 具体的元素:实现抽象元素的接口,还有一些自己的逻辑处理
  5. 对象结构:用于存放元素并遍历元素使其调用访问者,就是一个管理员一样

代码实现

以购买奶茶为例子,一个店铺里有3种会员等级,普通会员打9折,高级会员打8折,顶级会员打7折。

客户的购买流程是一样的,但是具体的计算价格的逻辑是不一样的,这个计算逻辑可能会频繁的改动,后期也可能增加新的会员等级,我们如果用类继承来写的话,改动旧代码就无法避免了。比如:之前的高级会员现在打75折,你是不是不得不改动之前的代码。

我们可以通过访问者模式实现,将变动的逻辑扩展出去。

首先先声明访问者,通过函数重载的方式声明不同的参数类型对应的返回结果

//抽象访问者:会员等级
interface Members {
  //奶茶价格
  drinksPrice(tea: MilkTea): number;
  //果茶价格
  drinksPrice(tea: FruitTea): number;
}

//普通会员
class RegularMembers implements Members {
  drinksPrice(tea: any): any {
    if (tea instanceof MilkTea) {
      return tea.getPrice() * 0.9;
    } else if (tea instanceof FruitTea) {
      return tea.getPrice() * 0.9;
    }
  }
}

//高级会员
class SeniorMember implements Members {
  drinksPrice(tea: any): any {
    if (tea instanceof MilkTea) {
      return tea.getPrice() * 0.8;
    } else if (tea instanceof FruitTea) {
      return tea.getPrice() * 0.8;
    }
  }
}

//顶级会员
class PremiumMember implements Members {
  drinksPrice(tea: any): any {
    if (tea instanceof MilkTea) {
      return tea.getPrice() * 0.7;
    } else if (tea instanceof FruitTea) {
      return tea.getPrice() * 0.7;
    }
  }
}

函数的重载可能还需要一些错误判断,这个就不细写了。

下面声明奶茶:

//抽象元素:茶饮料
abstract class Tea {
  //价格
  protected price: number;

  constructor(price: number) {
    this.price = price;
  }

  //或者价格
  public getPrice(): number {
    return this.price;
  }

  //扩展接口
  abstract accept(members: Members): void;
}

//奶茶
class MilkTea extends Tea {
  name = "奶茶";
  constructor(price: number) {
    super(price);
  }

  accept(members: Members) {
    return members.drinksPrice(this);
  }
}

//果茶
class FruitTea extends Tea {
  name = "果茶";
  constructor(price: number) {
    super(price);
  }

  accept(members: Members) {
    return members.drinksPrice(this);
  }
}

创建一个对象结构用于管理奶茶和会员之间的调用

//对象结构:奶茶店
class MilkTeashop {
  teaList: Tea[] = []; //客户点的奶茶

  //添加奶茶
  public add(tea: Tea) {
    this.teaList.push(tea);
  }

  //结账
  public invoicing(members: Members) {
    let totalPrice = 0;

    this.teaList.forEach((tea) => {
      totalPrice += tea.accept(members);
    });

    return totalPrice;
  }
}

使用:

//使用
class clinet {
  constructor() {
    //店铺实例
    const shop = new MilkTeashop();
    //奶茶实例
    const milkTea = new MilkTea(16);
    const fruitTea = new FruitTea(20);

    //客户下单
    shop.add(milkTea);
    shop.add(fruitTea);

    //结账
    //普通会员结账价格
    const total1 = shop.invoicing(new RegularMembers());
    //高级会员结账价格
    const total2 = shop.invoicing(new SeniorMember());
    //顶级会员结账价格
    const total3 = shop.invoicing(new PremiumMember());

    console.log(total1, total2, total3);
  }
}

new clinet();

最终打印结果:32.4 28.8 25.2

以后如果需要新增会员等级,只需要新增一个访问者子类就可以了,实现了数据与操作计算的解构,灵活性增强,但是相对的,每增加一个新的元素,比如新的奶茶,具体的访问者都得去增加相应的操作,这就导致增加新的元素是和困难的。

而且访问者依赖的是具体的类,而不是产品的接口,因为访问者需要知道每个类实例后的属性,如果是接口,那么参数就获取不全,所以这里又不符合依赖导致原则,但是访问者自身功能单一,可以扩展,符合单一职责。

应用场景

通常在以下情况可以考虑使用访问者(Visitor)模式。

  1. 对象结构相对稳定,但其操作算法经常变化的程序。
  2. 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。
  3. 对象结构包含很多类型的对象,希望对这些对象实施一些依赖于其具体类型的操作。

版权申明

本文系作者 @木灵鱼儿 原创发布在木灵鱼儿 - 有梦就能远航站点。未经许可,禁止转载。

关于作者

站点职位 博主
获得点赞 0
文章被阅读 401

相关文章