浅学设计模式之策略模式 (1/23)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/rikkatheworld/article/details/89521795

几天开始浅学设计模式,希望在毕业前能够稍微总结一下23种设计模式+一些设计原则。
总结的流程大概是:

  1. 学习概念
  2. 知道使用场景
  3. 写出模板代码、 画出其UML图
  4. 分析特点、优势

今天学的是策略模式
参考的blog有:深入解析策略模式

1、策略模式的概念

策略模式是一种定义一系列算法的方法,从概念上来看所有算法完成的都是相同的工作,只是实现有所不同(同目的不同路),它可以以相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合。

从这段话可以看出,策略模式定义好了算法家族,并将其封装起来,它们之间互不影响,此模式让算法的变化,不会影响到使用算法的客户,客户只需要调用最前台的类,便能实现想要的功能。从而达到需求。

2、策略模式的使用场景

从概念就可以看出,我们最最主要做的核心就是去封装算法。
也就是说,当算法只有两三个的时候,我们反而会白费力气去设计这个模式,
所以其使用场景当然是要我们封装n多个算法的,或者一开始只有一两个,但是随着时间的推移,我们会去不断的加算法的时候,就可以使用这个设计模式了。

举一个使用策略模式的背景(大话设计模式中的商场促销):

(1)问:假如我们给一个商场设计一个收银台,需要输入物品的单价(price)和数量(number)来计算用户要给多少钱?
答: 那这样很简单啊,我们只需要定义一个函数, return price*number 不就好了吗!

(2)问:假如我们现在五一打促销,物品五折出售,你来做下?
答:那不也差不多嘛,我做个checkbox,然后选择打5折,最后 return price*number*0.5 不就Ok

(3)问:那我现在又加一个,满300减100,或者满300-100的和打7折这样呢?
答:。。。

其实加单选框,然后根据每个需求去实现算法也是能实现需求的,但是笼统的去做会让代码耦合度高,如果我们再加需求,这份代码会不容易扩展,不容易维护。这个时候可以考虑使用策略模式。

  1. Strategy类,定义了所有算法的公共接口,比如说我有 打5折(A),打8折(B),满300-100(C),A、B、C三种算法的目的都是算出价格,那么我Strategy就可以定义算法接口 CalculateInterface,这些算法去实现它。
  2. Concrete类,继承自Strategy,封装了具体的算法或者行为
  3. Context类,用一个ConcreteStrategy来配置,维护一个队Strategy对象的引用。
  4. 用户只需要通过不同算法去实现策略就行了。

那么我们来同过上述例子写一个策略模式,首先构建其UML图:
在这里插入图片描述
这里面有一个Context类用于和用户交互,其依赖CashSuper,CashSuper时所有算法的共同的接口,我们一共有三个算法(正常,打折,满减),分别去实现接口并且完成实现。
(1)抽象策略:定义CashSuper接口代码如下:

public interface CashSuper {
    //收取现金,参数为原价,返回当前价
    public double acceptCash(double money);
}

(2)具体策略:接下来算法A、B、C去实现这个接口~

 /**
   * 满减
  */
public class CashReturn implements CashSuper{

    private double moneyCondition = 0;
    private double moneyReturn=0;
    
    //返利收费,初始化时必须输入返利条件和返回利值
    public CashReturn(double moneyCondition,double moneyReturn){
        this.moneyCondition = moneyCondition;
        this.moneyReturn = moneyReturn;
    }
    
    //算法实现
    public double acceptCash(double money) {
        double result = money;
        if (money >= moneyCondition) {
            result = money-(money/moneyCondition)*moneyReturn;
        }
        return result;
    }

}

 /**
   * 打折
  */
public class CashRebate implements CashSuper{

    private double moneyRebate=1;
    
    //打折收费初始化时必须输入折扣率
    public CashRebate(double moneyRebate){
        this.moneyRebate = moneyRebate;
    }
    
    public double acceptCash(double money) {
        return money * moneyRebate;
    }

}

  /**
   * 正常收费
  */
public class CashNormal implements CashSuper{

    public double acceptCash(double money) {
        return money;
    }

}

(3)接着实现Context类,维护这里面的CashSuper的引用,

public class CashContext {

    private CashSuper cashSuper;
    
    //通过构造方法传入具体的收费的策略
    public CashContext(CashSuper cashSuper){
        this.cashSuper = cashSuper;
    }
    
    //根据收费策略的不同获得计算的结果
    public double GetResult(double money){
        return cashSuper.acceptCash(money);
    }
    
}

到这里,我们已经完成了UML图中的分离算法的功能了

(4)最后去实现用户端(简化一下代码):
客户端去拿到一个CashContext上下文,等于获得一个CashSuper

        switch(string){
             case "正常收费"
                cc = new CashContext(new CashNormal());
                break;
             case "满300减100"
                cc = new CashContext(new CashReturn(300,100));   
                break;
             case "打8折"
                cc = new CashContext(new CashRebate(0.8));
        }
        //计算最后的结果
        double totalprice = 0;
        totalprice = cc.GetResult(price*number);
        System.out.println("totalprice:"+totalprice);

Ok,我们最后在客户端去判断具体使用哪个算法,根据算法我们可以拿到一个对应的封装好的上下文 crashcontext,最后通过其的 获取结果的方法GetResult来 算出结果。就实现好了一个策略模式了~

当然如果学习过简单工厂模式的同学可能会发现,我们最后在客户端的时候还要去判断算法,那客户使用的时候不就是要知道两个类吗?一个是CashContext及其使用方法,第二个是3个算法分别是做什么的,不然我就不知道在swith具体让cashcontext去实现哪个类。

这个时候如果能优化到,让客户只需要去知道CashContext就好了:客户直接传入原价还有类型type,然后取结果,而不是还要再去做swith分支判断
通过和简单工厂模式结合,我们可以在CashContext重新写一遍:

public class CashContext {

    private CashSuper cashSuper = null;
    
    //这里和工厂模式结合,修改其构造的传入函数
    public CashContext(String type){
       switch(string){
             case "正常收费"
                CashNormal cs0 = new CashNormal();
                cashSuper = cs0;
                break;
             case "满300减100"
                CashReturn cr1 = new CashReturn(300,100);
                cashSuper = cs0;
                break;
             case "打8折"
                CashRebate cr2 = new CashRebate(0.8));
                cashSuper = cr2;
                break;
        }
    }
    
    //根据收费策略的不同获得计算的结果
    public double GetResult(double money){
        return cashSuper.acceptCash(money);
    }
    
}

最后客户端只需要两行:

 CashContext ccContext = new CashContext(type);
 total = ccConext.GetResult(price*number);

这对客户端来说 就简直完美了。

3、画出UML图

别说了,上图:
在这里插入图片描述
那我们呢就是通过UML类图来写代码的,其实上面的商场促销模式就已经是策略模式中最经典的案例了,所以其实现代码就相当于是策略模式的模板代码了。
如果多去研究UML图,更加会对这个模式有更深入的理解吧。

4、总结特点

策略模式本质就是 分离算法,选择实现。

可以把策略模式看成两个部分

  1. 封装接口
    如果没有Context类,那么策略模式就是完全的IOP(面向接口编程),实现了分离算法的特点,我们根据需求去获取对应的接口。
  2. 选择实现
    但是没有Context类,客户就需要知道这些算法的作用了,不然在搞不清楚哪个算法用来做什么的时候,就等于用不了这个模式,所以我们用Context类来封装算法,客户只需传入最基本的参数,即使是个电脑小白,也能获取到结果。

策略模式又体现出两种设计原则

  1. 开闭-封闭原则:策略模式把一系列的可变算法进行封装,从而定义了良好的程序结构,在出现新的算法的时候,可以很容易的将新的算法实现加入到已有的系统中,而已有的实现不需要修改。
  2. 里氏替换原则:策略模式是一个扁平的结构,各个策略实现都是兄弟关系,实现了同一个接口或者继承了同一个抽象类。这样只要使用策略的客户端保持面向抽象编程,就可以动态的切换不同的策略实现以进行替换。

猜你喜欢

转载自blog.csdn.net/rikkatheworld/article/details/89521795