装饰者模式

什么是装饰者模式

所谓装饰者模式就是在不改变对象自身的基础上,允许向对象添加新功能的一种实现形式。

优点

  • 装饰者模式比继承灵活性,在不改变原有对象的情况下给对象扩展功能,符合开闭原则。
  • 装饰者模式可以动态使用不同的装饰类排列组合,创造出多样的行为组合。

缺点:

  • 增加系统的复杂性。
  • 对于多次装饰的对象,一旦出现错误,排错繁琐

功能实现

生活中有很多场景可以用装饰者模式来解释。比如你去星巴克买一杯咖啡,你可以选择要不要加糖、加奶等等,加了配料的价钱和不加的价钱肯定是不一样的。如果把一杯咖啡看做是一个类,那么我们可以用糖、奶这些配料来装饰。当然,这些配料会有很多种,也可以根据用户自己的喜好去添加,当然最终计算的金额肯定是不同的。

下面,就以这个场景为例,实现一个买咖啡计算金额的问题。

首先,需要创建一个咖啡类。

class Coffee {
  // 接收一个价格参数,用于创建咖啡的原始价格
  constructor(price) {
    this.price = price;
  }

  // 获取价格
  getPrice() {
    return this.price;
  }
}

这里,我们创建好了一个咖啡类,上线一个咖啡品种,我们可以new一个咖啡类。比如,新上了拿铁咖啡,定价为 38 元。

// 拿铁原味一杯定价38
const latte = new Coffee(38);
const price = latte.getPrice();
console.log(price); // 38

那么,我们如何给咖啡添加辅料?如果用装饰器的思维来考虑,期望实现如下形式。

// 拿铁原味一杯定价38
const latte = new Coffee(38);
// 加点糖
latte.decorate('sugar');
// 加点奶
latte.decorate('milk');
// 计算最终总价
const price = latte.getPrice();
console.log(price);

在这里,你可以任意组合装饰器,来实现不同的逻辑组合。

那么,对于每一种辅料,我们都需要给他实现一遍它的价格计算的逻辑,在咖啡类中,即Coffee的装饰器decorators。为了保证每一个实例之间不相互影响,我们将装饰器decorators挂载到类的实例方法上。

// 咖啡的装饰器
Coffee.decorators = {};

// 糖
Coffee.decorators.sugar = {
  getPrice(price) {
    return price + 2;
  },
};

// 奶
Coffee.decorators.milk = {
  getPrice(price) {
    return price + 4;
  },
};

// 折扣
Coffee.decorators.discount = {
  getPrice(price, rate = 1) {
    return price * rate;
  },
};

// 人民币支付
Coffee.decorators.toRMB = {
  getPrice(price) {
    return `¥${price.toFixed(2)}`;
  },
};

// 美元支付
Coffee.decorators.toDollar = {
  getPrice(price) {
    return `$${(price / 6.8).toFixed(2)}`;
  },
};

到这里,装饰器就基本完成了,现在需要在调用装饰器的时候,告诉咖啡类去应用装饰器。

我们可以在装饰器调用的时候,将装饰器注册到Coffee中的装饰器队列中,后续在获取价格的时候,在装饰器队列中去取到所有的装饰器计算汇总最终结果。

class Coffee {
  constructor(price) {
    this.price = price;
    // 装饰器队列
    this.ingredients = [];
  }

  getPrice() {
    let price = this.price;
    for (const [name, args] of this.ingredients) {
      // 将上一个价格传入下一个装饰器,并获取下一个装饰器计算的价格
      price = Coffee.decorators[name].getPrice(price, ...args);
    }
    // 汇总的价格
    return price;
  }

  /**
   * 注册装饰器
   * @param name 名称
   * @param args 装饰器需要的参数
   */
  decorate(name, ...args) {
    this.ingredients.push([name, args]);
  }
}

此时,我们可以实现预期的调用形式。完整代码如下:

class Coffee {
  constructor(price) {
    this.price = price;
    this.ingredients = [];
  }

  getPrice() {
    let price = this.price;
    for (const [name, args] of this.ingredients) {
      price = Coffee.decorators[name].getPrice(price, ...args);
    }
    return price;
  }

  decorate(name, ...args) {
    this.ingredients.push([name, args]);
  }
}

Coffee.decorators = {};

Coffee.decorators.sugar = {
  getPrice(price) {
    return price + 2;
  },
};

Coffee.decorators.milk = {
  getPrice(price) {
    return price + 4;
  },
};

Coffee.decorators.discount = {
  getPrice(price, rate = 1) {
    return price * rate;
  },
};

Coffee.decorators.toRMB = {
  getPrice(price) {
    return `¥${price.toFixed(2)}`;
  },
};

Coffee.decorators.toDollar = {
  getPrice(price) {
    return `$${(price / 6.8).toFixed(2)}`;
  },
};

// 拿铁原味一杯定价38
const latte = new Coffee(38);
// 加点糖
latte.decorate('sugar');
// 加点奶
latte.decorate('milk');
// 活动折扣,8折优惠
latte.decorate('discount', 0.8);
// 现金支付
latte.decorate('toRMB');
// 计算最终总价
const price = latte.getPrice();
console.log(price); // ¥35.20

本文完。😊

如果您觉得本文对您有用,欢迎捐赠或留言~
微信支付
支付宝

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注