设计模式行为型之策略模式
今天刚刚入职新公司,各个流程还没有走完,闲来无事就先来讲一讲策略模式。
策略模式其实是一种应该去“意会”的模式。
我们可以先来看看策略模式的定义:定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。
是不是看到这个定义会一脸蒙,没有关系,大部分同学看到这个定义的第一感觉都是这样,让我们先放下这个定义,来看看下面的例子吧。
在一家公司中,不同的员工对应的不同的职级,就拿差旅报销费用来举例,从 P1 到 p5 每个职级都是不一样的。
我们假设 p1 是 200 元一天,每升一级可以多加 50 元,那里 p2 就是 250 元,p3 就是 300 元 依次类推。
看看下面这个写法怎么样:
/**
* 职级对应差旅处理,输入职级,输出对应差旅费
*/
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;
}
}
这是一段非常容易理解的代码,五个职级对应五个差旅费。
但是我们此时需要再增加一个需求,要根据地区来做更细的调整,例如你如果去北上广,那么根据职级不同,还需要对差旅费进行调整。就像这样:
/**
* 职级对应差旅处理,输入职级,输出对应差旅费
* area 的值分别对应一类,二类,三类地区
*/
function travelExpense(level, area) {
if (level === "p1") {
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 这个魔鬼函数,同时你的每次修改都会影响整个函数功能,你不得不告诉测试同学需要重新测试全部流程。我想这时候测试同学的心里早就万马奔腾了。
在这种情况下,为了不被其他同事吐槽,也本着负责任的心态。我们需要来优化这段逻辑,
/**
* 差旅处理函数
*/
function travelExpense(level, area) {
if (level === "p1") {
return travelExpenseToP1(area);
}
if (level === "p2") {
return travelExpenseToP2(area);
}
if (level === "p3") {
return travelExpenseToP3(area);
}
if (level === "p4") {
return travelExpenseToP4(area);
}
if (level === "p5") {
return travelExpenseToP5(area);
}
}
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;
}
function travelExpenseToP2() {
//...
}
function travelExpenseToP3() {
//...
}
function travelExpenseToP4() {
//...
}
function travelExpenseToP5() {
//...
}
在上面的代码中,我们把不同 p 级的处理逻辑独立成一个个函数,在 travelExpense 中的逻辑就会大大减少,由此我们在这里可以了解到这样的处理方式是符合“单一功能”原则的。
让我们接着来考虑,我们独立出来了每个 P 级的逻辑,但是你发现没有,虽然在 travelExpense 中代码精简了不少,但实际他的处理方式并没有发生改变,如果此时我们需要添加 M 级别,那么又要在 travelExpense 中新增逻辑,那么测试仍然要重测整个 travelExpense 函数的功能,这一点并没有达到一个理想的要求。
如何解决上面我们提到的问题呢?想要不改动 travelExpense 的逻辑,即使我们添加了新的职级和其对应的功能,也不会影响到原有的功能。
也许有部分同学已经想到了,我们可以用映射的方式来实现,看看下面这段代码:
const travelExpenseObj = {
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: function(area) {
//...
},
p3: function(area) {
//...
},
p4: function(area) {
//...
},
p5: function(area) {
//...
},
};
function travelExpense(level, area) {
return travelExpenseObj[level](area);
}
通过这样映射的方式,即使后面需要添加其他级别的情况,还是针对某个级别的修改,我们都只需要改动 travelExpenseObj 对象,并且不会对其他的级别和整体功能造成影响。
在上面的例子中,每个职级都有不同的处理逻辑,但是他们之间又有相似的地方,并且区分他们的也就只有职级这一个条件。因此,当面对这种情况时我们不得已去使用大量的 if … else 时,我们就首先需要封装他们的逻辑,随后再通过映射的方式来避免大量的条件判断,这也就是策略模式的应用场景。
到这里这一讲也到了尾声,在前面的文章中我也强调过,如果你遇到某一点没有办法很快的明白,不妨先放下,继续向后看。本章也是,如果你仍然不能理解,那就先继续看下去吧。