设计模式行为型之策略模式

实验介绍

本实验为大家带来了策略模式,策略模式如果从定义上来看容易混乱,但其本身并不复杂。因此在一开始首先通过一个职级与区域划分差旅费用的实例为大家逐步展开策略模式的应用,通过这个实例就能很好的看到策略模式的应用方向。同时为大家指出了策略模式优点与缺点。在对应的情况下选择合适的模式才能起到事半功倍的效果。

知识点

  • 策略模式详解
  • 策略模式优缺点
  • 实验挑战

策略模式详解

策略模式理解难度并不大,但是单单从定义上来看却不容易明白。因此我们先来看一个真实的应用场景,通过这一实例可以引出该模式的定义与意图。

新建一个 index.html 文件,及 price.js 文件,并引入。

在一家公司中,不同的员工对应的不同的职级,就拿差旅报销费用来举例,从 P1 到 p5 每个职级都是不一样的。

我们假设 p1 是 200 元一天,每升一级可以多加 50 元,那么 p2 就是 250 元,p3 就是 300 元,依次类推。

先看一看这种写法如何实现上述功能:

// price.js

/**
 * 职级对应差旅处理,输入职级,输出对应差旅费
 */
function travelExpense(level) {
  if (level === "p1") {
    return 200;
  }
  if (level === "p2") {
    return 200 + 1 * 50;
  }
  if (level === "p3") {
    return 200 + 2 * 50;
  }
  if (level === "p4") {
    return 200 + 3 * 50;
  }
  if (level === "p5") {
    return 200 + 4 * 50;
  }
}

这是一段非常容易理解的代码,五个职级对应五个差旅费。可以看看具体效果如何:

// price.js

console.log(travelExpense("p1")); // 200
console.log(travelExpense("p2")); // 250
console.log(travelExpense("p3")); // 300
console.log(travelExpense("p4")); // 350
console.log(travelExpense("p5")); // 400

可以看到结果不出所料。但是我们此时需要再增加一个需求,要根据地区来做更细的调整,例如你如果去北上广,那么根据职级不同,还需要对差旅费进行调整。

具体规则是,地域分为一类、二类、三类这三种,其中一类地区多 200 ,二类地区多 100 ,三类地区多 50 。

那么一般情况下,我们在原函数中进行调整,就像这样:

// price.js

/**
 * 职级对应差旅处理,输入职级,输出对应差旅费
 * area 的值分别对应一类,二类,三类地区
 */
function travelExpense(level, area) {
  if (level === "p1") {
    // 处理 area 对应的差率费
    let price = 200;
    if (area === 1) {
      price += 200;
    } else if (area === 2) {
      price += 100;
    } else if (area === 3) {
      price += 50;
    }
    return price;
  }
  if (level === "p2") {
    // ... 省略 area 相关判断代码
    return 200 + 1 * 50;
  }
  if (level === "p3") {
    // ... 省略 area 相关判断代码
    return 200 + 2 * 50;
  }
  if (level === "p4") {
    // ... 省略 area 相关判断代码
    return 200 + 3 * 50;
  }
  if (level === "p5") {
    // ... 省略 area 相关判断代码
    return 200 + 4 * 50;
  }
}

我们仅为了展示,因此只在 p1 级别中添加了 area 判断的代码,但是你应该可以预测到,在其他等级中完整添加 area 判断后的代码,真的算得上是又臭又长。

同时可以试想一下,如果后期还需要增加或者删除其他逻辑,你只能去修改 travelExpense 这个魔鬼函数,同时你的每次修改都会影响整个函数功能,随后你不得不告诉测试同学需要重新测试全部流程。我想这时候测试同学的心里早就万马奔腾了。

在这种情况下,为了不被其他同事吐槽,也本着负责任的心态。我们需要来优化这段逻辑。

我们可以先抽离 p1 部分的代码:

// price.js

// 处理 p1 级别的差率费
function travelExpenseToP1(area) {
  let price = 200;
  if (area === 1) {
    price += 200;
  } else if (area === 2) {
    price += 100;
  } else if (area === 3) {
    price += 50;
  }
  return price;
}

同理我们可以抽离 p2 到 p5 级别:(省略了重复代码)

// price.js

// 处理 p2 级别的差率费
function travelExpenseToP2() {
  let price = 250;
  //...
}

// 处理 p3 级别的差率费
function travelExpenseToP3() {
  let price = 300;
  //...
}

// 处理 p4 级别的差率费
function travelExpenseToP4() {
  let price = 350;
  //...
}

// 处理 p5 级别的差率费
function travelExpenseToP5() {
  let price = 400;
  //...
}

在上面的代码中,我们把不同 p 级的处理逻辑独立成一个个函数,在 travelExpense 中的逻辑就会大大减少,由此我们在这里可以了解到这样的处理方式是符合“单一功能”原则的。

让我们接着来考虑。

尽管在上面我们独立出来了每个 P 级的逻辑,但是各位同学发现没有,虽然在 travelExpense 中代码精简了不少,但实际该函数的处理方式并没有发生改变。

如果此时我们需要添加新的 M 级别,那么又要在 travelExpense 中新增逻辑,那么测试仍然要重测整个 travelExpense 函数的功能,这一点并没有达到一个理想的要求。

如何解决上面我们提到的问题呢?想要不改动 travelExpense 的逻辑,即使我们添加了新的职级和其对应的功能,也不会影响到原有的功能。

也许有部分同学已经想到了,我们可以用映射的方式来实现。

同时添加 m1 级别,假设 m1 的起步差旅费为 1000 。

首先,我们需要一个映射对象:

// price.js

// 级别映射对象
const travelExpenseObj = {
  // 处理 p1 级别的差率费
  p1: function (area) {
    let price = 200;
    if (area === 1) {
      price += 200;
    } else if (area === 2) {
      price += 100;
    } else if (area === 3) {
      price += 50;
    }
    return price;
  },

  // 处理 p2 级别的差率费
  p2: function (area) {
    let price = 250;
    //...
  },

  // 处理 p3 级别的差率费
  p3: function (area) {
    let price = 300;
    //...
  },

  // 处理 p4 级别的差率费
  p4: function (area) {
    let price = 350;
    //...
  },

  // 处理 p5 级别的差率费
  p5: function (area) {
    let price = 400;
    //...
  },

  // 处理 m1 级别的差率费(新增)
  m1: function (area) {
    let price = 1000;
    if (area === 1) {
      price += 200;
    } else if (area === 2) {
      price += 100;
    } else if (area === 3) {
      price += 50;
    }
    return price;
  },
};

有了这个映射对象之后,我们该如何调用呢,可以这样:

// price.js

function travelExpense(level, area) {
  return travelExpenseObj[level](area);
}

现在让我们来看看这个新的实现方式是否能满足需求:

// price.js

// p1 级别的差率费
console.log(travelExpense("p1", 1)); // 400
console.log(travelExpense("p1", 2)); // 300
console.log(travelExpense("p1", 3)); // 250

// m1 级别的差率费
console.log(travelExpense("m1", 1)); // 1200
console.log(travelExpense("m1", 2)); // 1100
console.log(travelExpense("m1", 3)); // 1050

当然了,在上述的例子中,我们还可以把区域划分的部分抽离成公共方法:

// price.js

function dealArea(area) {
  if (area === 1) {
    return 200;
  }
  if (area === 2) {
    return 100;
  }
  if (area === 3) {
    return 50;
  }
}

修改对应的 travelExpense 的方法:

// price.js

const travelExpenseObj = {
  // 处理 p1 级别的差率费
  p1: function (area) {
    let price = 200;
    return price + dealArea(area);
  },

  // ...
};

function travelExpense(level, area) {
  return travelExpenseObj[level](area);
}

来看一下效果:

// price.js

// p1 级别的差率费
console.log(travelExpense("p1", 1)); // 400
console.log(travelExpense("p1", 2)); // 300
console.log(travelExpense("p1", 3)); // 250

通过这样映射的方式,即使后面需要添加其他级别的情况,还是针对某个级别的修改,我们都只需要改动 travelExpenseObj 对象,并且不会对其他的级别和整体功能造成影响。测试同学也仅需要测试对应级别的方法。

在上面的例子中,每个职级都有不同的处理逻辑,但是他们之间又有相似的地方,并且区分他们的也就只有职级这一个条件。因此,当面对这种情况时我们不得已去使用大量的 if … else 时,我们就首先需要封装他们的逻辑,随后再通过映射的方式来避免大量的条件判断,这也就是策略模式的应用场景。

到了这一步,相信大家已经对策略模式的具体使用有了一定的认识。策略模式主要解决的是:在有多种算法相似的情况下,使用 if...else 所带来的复杂和难以维护。

策略模式优缺点:

优点:

  1. 算法便于切换,通过映射的方式,和这样的调用方式:travelExpenseObj[level](area) 。可以非常方便的选择对应的职级和区域;
  2. 避免使用多重条件判断,在上述例子的最后实现上,避免了大量的 if...else 判断;
  3. 扩展性良好,在映射对象 travelExpenseObj 中,可以任意添加职级,并且对某些职级进行修改也不会造成其他的影响。

当然了,策略模式并不是全是有点,其缺点是:

  1. 随着类型增加,映射对象 travelExpenseObj 会越来越庞大;
  2. 所有策略都需要对外暴露,我们不得不暴露所有的职级与区域。

因此,在合适的时候选择合适的模式是很重要的。

实验挑战

策略模式理解起来并不困难。

为了体现映射对象的扩展性,请大家完善 m1 至 m3 这几个级别的差旅报价(m1:1000,m2:1500,m3:2000),m3 是最高级别,因此需要在返回 m3 差旅费用的同时输出:最高标准。

答案代码放在实验最后课程的源码包里,大家可以自行下载。

实验总结

本实验通过差旅费用报销的实例为大家生动的讲解了策略模式的应用场景,并且逐步递进,引导大家思考,而非一次性给出最终的实现方式。通过这样的过程,帮助大家理解,提升自身的思考能力。实验最后提到了策略模式的优缺点,只有在选择到合适模式的情况下才能提升我们的效率和规范。希望大家能通过本实验的学习,对策略模式有一个深刻的认识。

本节实验源码压缩包下载链接:策略模式源码

posted @   雨晨*  阅读(17)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
点击右上角即可分享
微信分享提示