Skip to content

Latest commit

 

History

History
169 lines (145 loc) · 5.86 KB

设计模式_策略模式.md

File metadata and controls

169 lines (145 loc) · 5.86 KB

定义:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。

假如正在开发一个在线商城的项目,每个产品都有原价,称之为 originalPrice。但实际上并非所有产品都以原价出售,可能会推出允许以折扣价出售商品的促销活动。
商家可以在后台为产品设置不同的状态,然后实际售价将根据产品状态和原价动态调整。
具体规则如下:

  • 部分产品已预售:为鼓励客户预订,将在原价基础上享受 20% 的折扣。
  • 部分产品处于正常促销阶段:如果原价低于或等于100,则以10%的折扣出售;如果原价高于 100,则减 10 元。
  • 有些产品没有任何促销活动:它们属于 default 状态,并以原价出售。

这时需要写一个获取商品价格的函数 getPrice ,应该怎么写呢?

function getPrice(originalPrice, status) {
    if (status === "pre-sale") {
        return originalPrice * 0.8;
    }

    if (status === "promotion") {
        if (origialPrice <= 100) {
            return origialPrice * 0.9;
        } else {
            return originalPrice - 20;
        }
    }

    if (status === "default") {
        return originalPrice;
    }
}

有三个条件,上面的代码写了三个 if 语句,这是非常直观的代码,但是这段代码组织上不好。
首先,它违反了单一职责原则(Single responsibility principle,规定每个类或者函数都应该有一个单一的功能,并且该功能应该由这个类或者函数完全封装起来)。函数 getPrice 做了太多的事情,这个函数不易阅读,也容易出现 bug 。如果一个条件出现 bug ,整个函数就会崩溃。同时,这样的代码也不容易调试。
并且这段代码很难应对变化的需求,这时就需要考虑设计模式,其往往会在业务逻辑发生变化时展现出它的魅力。
假设业务扩大了,现在还有另一个折扣促销:黑色星期五。折扣规则如下:

  • 价格低于或等于 100 元的产品以 20% 的折扣出售。
  • 价格高于 100 元但低于 200 元的产品将减少 20 元。
  • 价格高于或等于 200 元的产品将减少 20 元。

这个时候该怎么扩展 getPrice 函数呢?
看起来必须在 getPrice 函数中添加一个条件语句:

function getPrice(originalPrice, status) {
    if (status === "pre-sale") {
        return originalPrice * 0.8;
    }

    if (status === "promotion") {
        if (origialPrice <= 100) {
            return origialPrice * 0.9;
        } else {
            return originalPrice - 20;
        }
    }
    // 黑色星期五规则
    if (status === "black-friday") {
        if (origialPrice >= 100 && originalPrice < 200) {
            return origialPrice - 20;
        } else if (originalPrice >= 200) {
            return originalPrice - 50;
        } else {
            return originalPrice * 0.8;
        }
    }

    if (status === "default") {
        return originalPrice;
    }
}

每当增加或减少折扣时,都需要更改函数。这种做法违反了开闭原则(对扩展开放,对修改关闭)。修改已有的功能很容易出现新的错误,而且还会使得 getPrice 越来越臃肿。

那么如何优化这段代码呢?

首先,可以拆分这个函数 getPrice 以减少臃肿。

/**
 * 预售商品价格规则
 * @param {*} origialPrice
 * @returns
 */
function preSalePrice(origialPrice) {
    return originalPrice * 0.8;
}
/**
 * 促销商品价格规则
 * @param {*} origialPrice
 * @returns
 */
function promotionPrice(origialPrice) {
    if (origialPrice <= 100) {
        return origialPrice * 0.9;
    } else {
        return originalPrice - 20;
    }
}
/**
 * 黑色星期五促销规则
 * @param {*} origialPrice
 * @returns
 */
function blackFridayPrice(origialPrice) {
    if (origialPrice >= 100 && originalPrice < 200) {
        return origialPrice - 20;
    } else if (originalPrice >= 200) {
        return originalPrice - 50;
    } else {
        return originalPrice * 0.8;
    }
}
/**
 * 默认商品价格
 * @param {*} origialPrice
 * @returns
 */
function defaultPrice(origialPrice) {
    return origialPrice;
}

function getPrice(originalPrice, status) {
    if (status === "pre-sale") {
        return preSalePrice(originalPrice);
    }

    if (status === "promotion") {
        return promotionPrice(originalPrice);
    }

    if (status === "black-friday") {
        return blackFridayPrice(originalPrice);
    }

    if (status === "default") {
        return defaultPrice(originalPrice);
    }
}

经过这次修改,虽然代码行数增加了,但是可读性有了明显的提升。getPrice 函数显然没有那么臃肿,写单元测试也比较方便。 但是上面的改动并没有解决根本的问题:代码还是充满了 if-else ,而且当增加或者减少折扣规则的时候,仍然需要修改 getPrice。 其实使用这些 if-else 的目的就是为了对应状态和折扣策略。 从图中可以发现,这个逻辑本质上是一种映射关系:产品状态与折扣策略的映射关系。

可以使用映射而不是冗长的 if-else 来存储映射,按照这个思路可以构造一个价格策略的映射关系(策略名称与其处理函数之间的映射),如下:

const priceStrategies = {
    "pre-sale": preSalePrice,
    promotion: promotionPrice,
    "black-friday": blackFridayPrice,
    default: defaultPrice,
};
function getPrice(originalPrice, status) {
    return priceStrategies[status](originalPrice);
}

在什么情况下可以考虑使用策略模式呢?如果函数具有以下特征:

  • 判断条件很多
  • 各个判断条件下的代码相互独立 然后可以将每个判断条件下的代码封装成一个独立的函数,然后建立判断条件和具体策略的映射关系。

参考:https://juejin.cn/post/7113451841001619463