重构询价逻辑

现在我们基于我们已经学过的设计模式思想,一点一点改造掉这个臃肿的 askPrice。

单一功能改造

首先,我们赶紧把四种询价逻辑提出来,让它们各自为政:

  1. // 处理预热价
  2. function prePrice(originPrice) {
  3. if(originPrice >= 100) {
  4. return originPrice - 20
  5. }
  6. return originPrice * 0.9
  7. }
  8. // 处理大促价
  9. function onSalePrice(originPrice) {
  10. if(originPrice >= 100) {
  11. return originPrice - 30
  12. }
  13. return originPrice * 0.8
  14. }
  15. // 处理返场价
  16. function backPrice(originPrice) {
  17. if(originPrice >= 200) {
  18. return originPrice - 50
  19. }
  20. return originPrice
  21. }
  22. // 处理尝鲜价
  23. function freshPrice(originPrice) {
  24. return originPrice * 0.5
  25. }
  26. function askPrice(tag, originPrice) {
  27. // 处理预热价
  28. if(tag === 'pre') {
  29. return prePrice(originPrice)
  30. }
  31. // 处理大促价
  32. if(tag === 'onSale') {
  33. return onSalePrice(originPrice)
  34. }
  35. // 处理返场价
  36. if(tag === 'back') {
  37. return backPrice(originPrice)
  38. }
  39. // 处理尝鲜价
  40. if(tag === 'fresh') {
  41. return freshPrice(originPrice)
  42. }
  43. }

OK,我们现在至少做到了一个函数只做一件事。现在每个函数都有了自己明确的、单一的分工:

  1. prePrice - 处理预热价
  2. onSalePrice - 处理大促价
  3. backPrice - 处理返场价
  4. freshPrice - 处理尝鲜价
  5. askPrice - 分发询价逻辑

如此一来,我们在遇到 Bug 时,就可以做到“头痛医头,脚痛医脚”,而不必在庞大的逻辑海洋里费力去定位到底是哪块不对。
同时,如果我在另一个函数里也想使用某个询价能力,比如说我想询预热价,那我直接把 prePrice 这个函数拿去调用就是了,而不必在 askPrice 肥胖的身躯里苦苦寻觅、然后掏出这块逻辑、最后再复制粘贴到另一个函数去——更何况万一哪天 askPrice 里的预热价逻辑改了,你还得再复制粘贴一次,扎心啊老铁!
到这里,在单一功能原则的指引下,我们已经解决了一半的问题。
我们现在来捋一下,其实这个询价逻辑整体上来看只有两个关键动作:

  1. 询价逻辑的分发 ——> 询价逻辑的执行

在改造的第一步,我们已经把“询价逻辑的执行”给摘了出去,并且实现了不同询价逻辑之间的解耦。接下来,我们就要拿“分发”这个动作开刀。

开放封闭改造

剩下一半的问题是啥呢?就是咱们上面说的那个新人价的问题——这会儿我要想给 askPrice 增加新人询价逻辑,我该咋整?我只能这么来:

  1. // 处理预热价
  2. function prePrice(originPrice) {
  3. if(originPrice >= 100) {
  4. return originPrice - 20
  5. }
  6. return originPrice * 0.9
  7. }
  8. // 处理大促价
  9. function onSalePrice(originPrice) {
  10. if(originPrice >= 100) {
  11. return originPrice - 30
  12. }
  13. return originPrice * 0.8
  14. }
  15. // 处理返场价
  16. function backPrice(originPrice) {
  17. if(originPrice >= 200) {
  18. return originPrice - 50
  19. }
  20. return originPrice
  21. }
  22. // 处理尝鲜价
  23. function freshPrice(originPrice) {
  24. return originPrice * 0.5
  25. }
  26. // 处理新人价
  27. function newUserPrice(originPrice) {
  28. if(originPrice >= 100) {
  29. return originPrice - 50
  30. }
  31. return originPrice
  32. }
  33. function askPrice(tag, originPrice) {
  34. // 处理预热价
  35. if(tag === 'pre') {
  36. return prePrice(originPrice)
  37. }
  38. // 处理大促价
  39. if(tag === 'onSale') {
  40. return onSalePrice(originPrice)
  41. }
  42. // 处理返场价
  43. if(tag === 'back') {
  44. return backPrice(originPrice)
  45. }
  46. // 处理尝鲜价
  47. if(tag === 'fresh') {
  48. return freshPrice(originPrice)
  49. }
  50. // 处理新人价
  51. if(tag === 'newUser') {
  52. return newUserPrice(originPrice)
  53. }
  54. }

在外层,我们编写一个 newUser 函数用于处理新人价逻辑;在 askPrice 里面,我们新增了一个 if-else 判断。可以看出,这样其实还是在修改 askPrice 的函数体,没有实现“对扩展开放,对修改封闭”的效果。
那么我们应该怎么做?我们仔细想想,楼上用了这么多 if-else,我们的目的到底是什么?是不是就是为了把 询价标签-询价函数 这个映射关系给明确下来?那么在 JS 中,有没有什么既能够既帮我们明确映射关系,同时不破坏代码的灵活性的方法呢?答案就是对象映射
咱们完全可以把询价算法全都收敛到一个对象里去嘛:

  1. // 定义一个询价处理器对象
  2. const priceProcessor = {
  3. pre(originPrice) {
  4. if (originPrice >= 100) {
  5. return originPrice - 20;
  6. }
  7. return originPrice * 0.9;
  8. },
  9. onSale(originPrice) {
  10. if (originPrice >= 100) {
  11. return originPrice - 30;
  12. }
  13. return originPrice * 0.8;
  14. },
  15. back(originPrice) {
  16. if (originPrice >= 200) {
  17. return originPrice - 50;
  18. }
  19. return originPrice;
  20. },
  21. fresh(originPrice) {
  22. return originPrice * 0.5;
  23. },
  24. };

当我们想使用其中某个询价算法的时候:通过标签名去定位就好了:

  1. // 询价函数
  2. function askPrice(tag, originPrice) {
  3. return priceProcessor[tag](originPrice)
  4. }

如此一来,askPrice 函数里的 if-else 大军彻底被咱们消灭了。这时候如果你需要一个新人价,只需要给 priceProcessor 新增一个映射关系:

  1. priceProcessor.newUser = function (originPrice) {
  2. if (originPrice >= 100) {
  3. return originPrice - 50;
  4. }
  5. return originPrice;
  6. }

这样一来,询价逻辑的分发也变成了一个清清爽爽的过程。当李雷以这种方式新增一个新人价的询价逻辑的时候,就可以底气十足地对测试同学说:老哥,我改了询价逻辑,但是改动范围仅仅涉及到新人价,是一个单纯的功能增加。所以你只测这个新功能点就 OK,老逻辑不用管!
从此,李雷就从人人喊打的 if-else 侠,摇身一变成为了测试之友、中国好开发。业务代码里的询价逻辑,也因为李雷坚守设计原则100年不动摇,而变得易读、易维护。