一个阳光明媚的上午,靓仔正在开心的划水摸鱼,耳机里传来音乐“不是吧不是吧,难道单压也算压......”

略懂设计模式之策略模式-LMLPHP

产品经理突然出现在身后,拍了拍我的肩膀

产品经理:又在摸鱼,看来工作不饱和啊,正好有个需求你做一下。公司推出了会员制度,分普通会员和超级会员,普通会员购买商品打九折,超级会员购买商品打八折。

靓仔:就这?简单

public Double computePrice(String type, Double price) {
    if ("VIP".equals(type)) {
        // 普通会员9折优惠
        return price * 0.9;
    } else if ("S_VIP".equals(type)) {
        // 超级会员8折优惠
        return price * 0.8;
    } else {
        // 非会员无优惠
        return price;
    }
}

几天后。。。

产品经理:我们增加了活动送的一个月体验会员,与普通会员享受同等九折待遇,但是只能享受最高20的优惠金额。

靓仔:明白,不就是“穷逼vip”吗

于是就在原来的代码上改了改

public Double computePrice(String type, Double price) {
    if ("VIP".equals(type)) {
        // 普通会员9折优惠
        return price * 0.9;
    } else if ("S_VIP".equals(type)) {
        // 超级会员8折优惠
        return price * 0.8;
    } else if ("BEGGAR_VIP".equals(type)) {
        // 穷逼vip 9折优惠,最大优惠金额20
        return price > 200 ? (price - 20) : price * 0.9;
    }else {
        // 非会员无优惠
        return price;
    }
}

有没有觉得 if-else 特别多,而且一旦再增加会员种类,那么看上去就更繁琐,代码耦合严重,维护起来十分不方便。

怎么办,重构一下代码呗

首先提取出价格计算的接口类

public interface PriceStrategy {
    Double computePrice(Double price);
}

然后针对不同的会员类型,实现不同会员价格计算接口,提供算法

// 普通会员
public class VipPriceStrategy implements PriceStrategy{
    @Override
    public Double computePrice(Double price) {
        return price * 0.9;
    }
}

// 超级会员
public class SVipPriceStrategy implements PriceStrategy{
    @Override
    public Double computePrice(Double price) {
        return price * 0.8;
    }
}

// 穷逼会员
public class BeggarVipPriceStrategy implements PriceStrategy{
    @Override
    public Double computePrice(Double price) {
        return price > 200 ? (price - 20) : price * 0.9;
    }
}

增加一个上下文角色,封装算法对高层屏蔽,高层模块只用访问Context

public class PriceContext {
    private PriceStrategy priceStrategy;

    public PriceContext(PriceStrategy priceStrategy) {
        this.priceStrategy = priceStrategy;
    }

    public Double computePrice(Double price) {
        return priceStrategy.computePrice(price);
    }
}

外部调用,计算价格

public Double getPrice(String type, Double price) {
    PriceStrategy priceStrategy;
    if ("VIP".equals(type)) {
        priceStrategy = new VipPriceStrategy();
    } else if ("S_VIP".equals(type)) {
        priceStrategy = new SVipPriceStrategy;
    } else if ("BEGGAR_VIP".equals(type)) {
        priceStrategy = new BeggarVipPriceStrategy;
    } else {
        return price;
    }
    PriceContext priceContext = new PriceContext(priceStrategy);
    return priceContext.computePrice(price);
}

没错,这就是策略模式

  • 环境(Context):持有一个 Strategy 的引用。
  • 抽象策略(Strategy):这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
  • 具体策略(ConcreteStrategy):包装了相关的算法或行为。

略懂设计模式之策略模式-LMLPHP

有朋友可能会问了,这和工厂模式有什么区别吗?

我们再来看下工厂模式。

简单工厂模式:

略懂设计模式之策略模式-LMLPHP

看上去简直一摸一样吧。

其实工厂模式和设计模式一直给人一种错觉,总感觉是一样的,没有丝毫的区别。

直到我看到一个网友的解读:

一个注重的是实例的生产,一个注重的是策略方法。

好了,这个时候再来看我们的代码,好像越来越复杂了,虽然用策略模式将具体的算法都抽离出来了,但是 if-else 的问题还是没有解决啊

思考一下,我们可不可以结合以下工厂模式,来去掉烦人的 if-else

可以把策略对象初始化到一个 map 进行管理

public interface PriceStrategy {
    Map<String, PriceStrategy> map = new ConcurrentHashMap(){{
        put("VIP", new VipPriceStrategy());
        put("S_VIP", new SVipPriceStrategy());
        put("BEGGAR_VIP", new BeggarVipPriceStrategy());
    }};

    Double computePrice(Double price);
}

public class PriceContext {
    private PriceStrategy priceStrategy;

    public PriceContext(String type) {
        this.priceStrategy = PriceStrategy.map.get(type);
    }

    public Double computePrice(Double price) {
        return priceStrategy.computePrice(price);
    }
}

外部调用

public Double computePrice(String type, Double price) {
    PriceContext priceContext = new PriceContext(type);
    return priceContext.computePrice(price);
}

舒服了,终于干掉了 if-else

策略模式优缺点

优点:

  • 策略模式遵循开闭原则,实现代码的解耦合,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为。
  • 算法的使用就和算法本身分开,符合单一职责原则

缺点:

  • 客户端必须知道所有的策略类,并自行决定使用哪一个策略类
  • 策略模式可能会造成系统产生很多具体策略类

总结

其实我们在工作中使用设计模式的时候,不需要被条条框框所束缚,设计模式可以有很多变种,也可以结合几种设计模式一起使用,别忘了使用设计模式的初衷是什么,不要为了使用设计模式而使用设计模式。

END

往期推荐

基于 Mysql 实现一个简易版搜索引擎

如何保证接口的幂等性?

你必须了解的分布式事务解决方案

就这?分布式 ID 发号器实战

略懂设计模式之工厂模式

09-26 03:45