设计模式之策略模式

设计模式之策略模式

情景模式:

设计一个超市收银系统,超市可能会根据不同节日给出不同的折扣

代码:

@Setter
public class Market {

    private double price;

    private double discount=1;

    public double getPriceAfterDiscount() {
        return price * discount;
    }
}

分析:

需求非常简单,用户可以根据需要输入相关的折扣,然后获取到最终的价格。但是,如果这个系统就是用来专门做超市收银,也就是说Market类是本系统的核心类的话,那上面的代码就太过简单了:扩展性不高,作为系统的核心类,需求肯定会变,比如可能后面不仅仅是打折,可能还有积分活动,VIP活动等等,那个时候就得重构代码

情景模式二:

设计一个超市系统,超市可以根据用户购买的金额进行满减,并且用户可以通过积分进行一定额度的抵扣,并且在不同的节假日可以一定的折扣活动。

分析:正如上面所说的一样,随着超市的活动形式的增加,折扣的策略越来越多,如果我们依然按照上面的代码修改,会导致Market这个类越来越臃肿,这样严重的违背了SOLID设计原则。因此我们可以对代码进行修改:

代码:

首先抽象出打折接口:

public interface Strategy {

     double getPriceAfterDiscount();

     void setPrice(double price);
}

这里为了方便,抽象出一个统一的抽象类:

public abstract class AbstractStrategy implements Strategy {

    protected double price;

    public  void setPrice(double price){
        this.price=price;
    }

    public abstract double getPriceAfterDiscount();
}

定义打折类:

public class StrategyByDiscount extends AbstractStrategy {

    private double disCount;

    @Override
    public double getPriceAfterDiscount() {
        return disCount*price;
    }

    public  StrategyByDiscount(double disCount){
        this.disCount=disCount;
    }
}

定义满减类:

public class StrategyByPriceBreak extends AbstractStrategy{

    private  double limit;

    private  double reduce;

    public void setLimit(double limit){
        this.limit=limit;
    }

    public  void setReduce(double reduce){
        this.reduce=reduce;
    }

    @Override
    public double getPriceAfterDiscount() {
        return price-((price/limit)*reduce);
    }

    public  StrategyByPriceBreak(double limit,double reduce){
        this.limit=limit;
        this.reduce=reduce;
    }
}

最后,根据用户用户选项,生成对应的类:

public class StrategyFactory {

    public static Strategy createStrategy(double price, String strategyStr) {
        Strategy strategy = null;
        switch (strategyStr) {
            case "打八折":
                strategy = new StrategyByDiscount(0.8);
                break;
            case "打九折":
                strategy = new StrategyByDiscount(0.9);
                break;
            case "每满100减20":
                strategy = new StrategyByPriceBreak(100, 20);
                break;
            default:
                strategy = new StrategyByDiscount(1);
        }

        strategy.setPrice(price);
        return  strategy;
    }
}

分析:上面其中便是简单工厂的解决方案,使用简单工厂,我们能够生成对应的类,执行不同的操作,在以后的开发中,需要添加新的策略,直接继承并实现新的类即可,基本完成需求。


但是,到这里并没有完,Market所做的并不只是这些,还有一些通用的功能,比如扫描条形码获取商品价格,记录会员信息,添加新的商品,判断商品是否过期等等。这些信息,和商品是否打折并不相关,不管价格打折与否,上面的方法都是通用的,因此,此时我们不应该是简单的把Strategy交给用户直接使用,而是应该将其作为一个属性,传递给Market类使用:

public class Market {

    private Strategy strategy;

    public  Market(Strategy strategy){
        this.strategy=strategy;
    }   

    public  double getTotalPrice(){
        return strategy.getPriceAfterDiscount();
    }

    public  double getSingleGoodsPrice(String key){
        //...获取单个商品信息
        return 0;
    }

    public Date getAcquisitionDate(String key){
        //...获取商品生产日期
        return null;
    }

}

其中,Market可以和StrategyFactory结合起来,从而使用户更加便于使用:

public class Market{
    private Strategy strategy;

    public  Market(String strategyStr){
        this.strategy=StrategyFactory.createStrategy(strategyStr);
    }    
}

以上示例代码均在:https://github.com/dengchengchao/design-pattern


策略模式

从上面的代码中我们可以发现,通过封装不同的Strategy类,我们实现了不同的打折策略。

但是对于Market类来说,并没有什么影响,它完全不关心所传入的Strategy的具体实现,而只用在需要调用它的地方调用相应的方法即可,我们结合了简单工厂生成对应的对象,传入给Market,而Market类中保存的都是与打折无关的通用的方法。


Q:明明用继承也能实现。为什么这里要用变量的形式传入

A:因为组合优于继承,在能使用组合的情况下,最好优先使用继承,需要使用继承的情况也有,当存在多处需要变化的时候,可以使用继承,这便是后面需要学习的模板方法模式

定义:定义一系列算法,将每个算法封装到具有公共接口的一系列策略中,从而使他们可以相互替换/或是让算法在不想影响客户端的情况下发生变化

策略模式是对象行为型模式

UML图:

image

  • Context作为一个总的环境类,表示需要使用Strategy对象的类

  • Strategy表示策略接口

  • ConcreteStrategeA:表示具体的实现类

针对场景:

  • 如果一个对象有很多类,而这些类的区别仅仅在于其某个行为不同,此时可以将变化抽象出来,将不变封装起来。
  • 一个系统需要动态的在几种算法中选择其中一种

策略模式的核心思想,便是通过面向接口编程,将变化的东西封装起来,从而实现封装变化,策略模式也一般用于只有少量的变化地方,如果变化的内容过多,可能就需要使用模板方法替代。

在策略模式中,包含了三个角色:

  • 环境类:真正使用策略的地方
  • 策略接口
  • 具体的策略实现

可以看出来,策略模式是典型的组合大于继承和面向接口编程的体现

策略类是一个比较简单的对象行为型的类,即使没有学习过设计模式,也可能在日常编码中用到过。