首页 > 其他分享 >设计模式行为型之策略模式

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

时间:2024-02-23 18:15:44浏览次数:26  
标签:200 p1 return 策略 area price 模式 设计模式 travelExpense

实验介绍

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

知识点

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

策略模式详解

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

新建一个 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 差旅费用的同时输出:最高标准。

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

实验总结

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

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

标签:200,p1,return,策略,area,price,模式,设计模式,travelExpense
From: https://www.cnblogs.com/xzemt/p/18030134

相关文章

  • 设计模式前言
    基本概念设计模式是什么?相信这是每一个同学在刚开始学习设计模式的时候都会存在的疑问,单单从名字上来看这确实会让人感觉是一门十分高大上的学问,但是真的是这样吗?答案当然是否定的。相反,设计模式十分的接地气,可以说它存在于我们生活中的方方面面。在《设计模式:可复用面向对象......
  • 设计模式结构型之装饰器模式
    实验介绍本实验主要为大家介绍设计模式中的装饰器模式。从装饰器的概念引入,详细的介绍了装饰器和装饰器的应用,帮助大家对其有一个深层的理解。随后提供了两个在实际开发过程中可能会遇到的真实场景,帮助大家建立装饰器模式在前端应用的直观印象。最后提供了使用装饰器时候需要注意......
  • 设计模式结构型之适配器模式
    实验介绍本节实验为大家带来了适配器模式,适配器模式是作为两个不兼容的接口之间的桥梁,可以将变化都封装于它本身,提供简单统一的接口使用。从一个有趣的例子开始为大家逐步的讲解适配器,帮助大家学习其基本的概念。随后为大家介绍了适配器在前端中的真实应用,加深对适配器的认识。最......
  • 设计模式创建型之原型模式
    实验介绍本实验主要为大家介绍了前端中原型模式,为了加深大家对原型的了解,实验中花费大量篇幅讲解了原型及原型的概念,并配上了相关的例子以帮助大家学习。随后我们对class进行了简单的介绍,它可以被简单的认为是语法糖。最后,为了帮助大家理解原型中的克隆,实验也对浅拷贝与深拷贝......
  • 设计模式创建型之工厂模式
    基本概念在给出工厂模式的定义之前,不妨先来了解一下工厂的概念。通过百度百科查到的所谓工厂的定义:是一类用以生产货物的大型工业建筑物,即我们为工厂输送原料,经过工厂对原料进行处理加工之后会输出产物。例如下面这样一个例子:张三是一名大学生,毕业后为了上班方便就考虑买一台......
  • 设计模式创建型之单例模式
    实验介绍本实验主要介绍了设计模式中的单例模式,在前端领域中,有很多地方都运用到了单例模式的思维,例如目前的主流前端框架中所用到的Redux和Vuex。实验首先通过一个小例子为大家展示了单例模式的实现原理,随后通过完成一个自定义的Storage存储器来帮助大家加深对单例模式的理......
  • 3分钟看懂设计模式02:观察者模式
    一、什么是观察者模式观察者模式又叫做发布-订阅模式或者源-监视器模式。结合它的各种别名大概就可以明白这种模式是做什么的。其实就是观察与被观察,一个对象(被观察者)的状态改变会被通知到观察者,并根据通知产生各自的不同的行为。以下为《设计模式的艺术》中给出的定义:观察者......
  • 打造纯Lua组件化开发模式:Unity xLua框架详解
    在传统的Unity开发中,通常会使用C#来编写游戏逻辑和组件。但是,随着Lua在游戏开发中的应用越来越广泛,我们可以将游戏逻辑和组件完全用Lua来实现,实现纯Lua的组件化开发模式。这样做的好处是可以更加灵活地修改游戏逻辑,而不需要重新编译C#代码。 3.实现步骤对啦!这里有个游戏开......
  • windows11系统下ppt突然打不开,报错“PowerPoint启动时提示上次启动失败是否启用安全模
    问题描述:windows11系统下ppt突然打不开,报错:PowerPoint启动时提示上次启动失败是否启用安全模式...ppt文件是正常的,我发到其他设备上是可以正常打开的。我把office卸载了用腾讯文档也无法打开ppt文件。点击用安全模式打开后无反应,即无错误提示也没有打开ppt。以上情况说明:......
  • 读论文-基于序列模式的电子商务推荐系统综述(A Survey of Sequential Pattern Based E
    前言今天读的论文为一篇于2023年10月3日发表在《算法》(Algorithms)的论文,这篇文章综述了基于序列模式的电子商务推荐系统,强调了通过整合用户购买和点击行为的序列模式来提高推荐准确性、减少数据稀疏性、增加推荐新颖性,并改善推荐系统的可扩展性。文章详细分析了现有推荐系统的......