策略模式-设计模式

在讲述之前,我们首先看小例子:

现实生活中我们去商场上买东西的时候,卖场经常根据不同的客户来制定不同的报价策略,比如新客户不打折扣,针对老客户打9折,针对VIP打8折……

现在我们做一个报价管理模块,简要点就是针对不同的客户,提供不同的报价。

假如是有你来做,你会怎么做?
在日常的开发中,我们大部分会写出如下的代码片段:

public class QuoteManager {
    public BigDecimal quote(BigDecimal originalPrice,String customType){
        if ("新客户".equals(customType)) {
            System.out.println("抱歉!新客户没有折扣!");
            return originalPrice;
        }else if ("老客户".equals(customType)) {
            System.out.println("恭喜你!老客户打9折!");
            originalPrice = originalPrice.multiply(new BigDecimal(0.9)).setScale(2,BigDecimal.ROUND_HALF_UP);
            return originalPrice;
        }else if("VIP客户".equals(customType)){
            System.out.println("恭喜你!VIP客户打8折!");
            originalPrice = originalPrice.multiply(new BigDecimal(0.8)).setScale(2,BigDecimal.ROUND_HALF_UP);
            return originalPrice;
        }
        //其他人员都是原价
        return originalPrice;
    }
}

我们会在日常的开发中大量使用if ,else if,else if,else……上面的代码工作的很好,但是呢,经过大量的判断操作,也是有问题的,将不同客户的报价的算法都放在了同一个方法里面,使得该方法随着业务的拓展会变得越来越臃肿,时刻更改着此处代码,达不到复用性。

放看一下上面的改进,我们将不同客户的报价算法单独作为一个方法。

 

public class QuoteManagerImprove {
    public BigDecimal quote(BigDecimal originalPrice, String customType){
        if ("新客户".equals(customType)) {
            return this.quoteNewCustomer(originalPrice);
        }else if ("老客户".equals(customType)) {
            return this.quoteOldCustomer(originalPrice);
        }else if("VIP客户".equals(customType)){
            return this.quoteVIPCustomer(originalPrice);
        }
        //其他人员都是原价
        return originalPrice;
    }

    /**
     * 对VIP客户的报价算法
     * @param originalPrice 原价
     * @return 折后价
     */
    private BigDecimal quoteVIPCustomer(BigDecimal originalPrice) {
        System.out.println("恭喜!VIP客户打8折");
        originalPrice = originalPrice.multiply(new BigDecimal(0.8)).setScale(2,BigDecimal.ROUND_HALF_UP);
        return originalPrice;
    }

    /**
     * 对老客户的报价算法
     * @param originalPrice 原价
     * @return 折后价
     */
    private BigDecimal quoteOldCustomer(BigDecimal originalPrice) {
        System.out.println("恭喜!老客户打9折");
        originalPrice = originalPrice.multiply(new BigDecimal(0.9)).setScale(2,BigDecimal.ROUND_HALF_UP);
        return originalPrice;
    }

    /**
     * 对新客户的报价算法
     * @param originalPrice 原价
     * @return 折后价
     */
    private BigDecimal quoteNewCustomer(BigDecimal originalPrice) {
        System.out.println("抱歉!新客户没有折扣!");
        return originalPrice;
    }

}

 

上面的代码比刚开始的时候好一点,将每个具体的算法都单独抽出来,当某个具体的算法出现变动问题,只需要修改相应的算法就可以啦。

但是改进后的代码还是有问题的,有什么问题呢?

当我们新增一个客户类型,首先要添一个该客户类型的报价算法,然后再quote方法中再次添加一个else if ,这就违反了设计原则--开闭原则(open-closed - principle)

开闭原则

对于扩展是开放的,这意味着模块的行为是可以拓展的,当应用的需求改变时候,可以对模块进行扩展。

对于修改是关闭的,对模块行为进行扩展时 ,不用改动模块的源代码或者二进制代码。

那有没有办法可适用于可扩展,可维护,又可以方便的响应变化呢,下面就是我们要讲的策略模式。

 

策略模式

定义:

策略模式定义了一系列的算法,把它们一个个封装起来,并且使他们可互相替换(变化)。该模式使得算法可独立于使用它的客户程序(稳定)而变化(扩展,子类化)。

结构:

  1. 策略接口角色IStrategy:用来约束一系列具体策略算法,策略上下文角色ConcreteStrategy使用此策略接口来调用具体实现的算法。
  2. 具体策略实现角色ConcreteStrategy:具体的策略实现,即算法的具体实现。
  3. 策略上下文角色StrategyContext:策略上下文,负责和具体的策略实现了交互,通常策略上下文对象会持有一个真正的策略实现对象,策略上下文还可以让具体的策略实现从中获取相关数据,回调了策略上下文对象的方法。

UML类图

 

策略模式代码的一般实现 

package strategy.examp01;
//策略接口
public interface IStrategy {
    //定义的抽象算法方法 来约束具体的算法实现方法
    public void algorithmMethod();
}

具体的策略实现:

package strategy.examp01;
// 具体的策略实现
public class ConcreteStrategy implements IStrategy {
    //具体的算法实现
    @Override
    public void algorithmMethod() {
        System.out.println("this is ConcreteStrategy method...");
    }
}
package strategy.examp01;
// 具体的策略实现2
public class ConcreteStrategy2 implements IStrategy {
     //具体的算法实现
    @Override
    public void algorithmMethod() {
        System.out.println("this is ConcreteStrategy2 method...");
    }
}
package strategy.examp01;
/**
 * 策略上下文
 */
public class StrategyContext {
    //持有一个策略实现的引用
    private IStrategy strategy;
    //使用构造器注入具体的策略类
    public StrategyContext(IStrategy strategy) {
        this.strategy = strategy;
    }
   public void contextMethod(){
        //调用策略实现的方法
        strategy.algorithmMethod();
    }
}

 

客户端使用:

package strategy.examp01;
//外部客户端
public class Client {
    public static void main(String[] args) {
        //1.创建具体测策略实现,多态使用
        IStrategy strategy = new ConcreteStrategy2();
        //2.在创建策略上下文的同时,将具体的策略实现对象注入到策略上下文当中
        StrategyContext ctx = new StrategyContext(strategy);
        //3.调用上下文对象的方法来完成对具体策略实现的回调
        ctx.contextMethod();
    }
}

针对一开始所讲的例子:可以使用策略模式对其进行改造,不同的类型的客户有不同的折扣,可以将不同类型的客户报价规则都封装为一个独立的算法,然后抽象出报价算法的公共接口。

公共报价的策略接口:

package strategy.examp02;
import java.math.BigDecimal;
//报价策略接口
public interface IQuoteStrategy {
    //获取折后价的价格
    BigDecimal getPrice(BigDecimal originalPrice);
}

新客户报价策略实现:

package strategy.examp02;
import java.math.BigDecimal;
//新客户的报价策略实现类
public class NewCustomerQuoteStrategy implements IQuoteStrategy {
    @Override
    public BigDecimal getPrice(BigDecimal originalPrice) {
        System.out.println("抱歉!新客户没有折扣!");
        return originalPrice;
    }
}

老客户报价策略实现:

package strategy.examp02;
import java.math.BigDecimal;
//老客户的报价策略实现
public class OldCustomerQuoteStrategy implements IQuoteStrategy {
    @Override
    public BigDecimal getPrice(BigDecimal originalPrice) {
        System.out.println("恭喜!老客户享有9折优惠!");
        originalPrice = originalPrice.multiply(new BigDecimal(0.9)).setScale(2,BigDecimal.ROUND_HALF_UP);
        return originalPrice;
    }
}

VIP客户报价策略实现:

package strategy.examp02;
import java.math.BigDecimal;
//VIP客户的报价策略实现
public class VIPCustomerQuoteStrategy implements IQuoteStrategy {
    @Override
    public BigDecimal getPrice(BigDecimal originalPrice) {
        System.out.println("恭喜!VIP客户享有8折优惠!");
        originalPrice = originalPrice.multiply(new BigDecimal(0.8)).setScale(2,BigDecimal.ROUND_HALF_UP);
        return originalPrice;
    }
}

报价上下文:

package strategy.examp02;
import java.math.BigDecimal;
//报价上下文角色
public class QuoteContext {
    //持有一个具体的报价策略
    private IQuoteStrategy quoteStrategy;

    //注入报价策略
    public QuoteContext(IQuoteStrategy quoteStrategy){
        this.quoteStrategy = quoteStrategy;
    }

    //回调具体报价策略的方法
    public BigDecimal getPrice(BigDecimal originalPrice){
        return quoteStrategy.getPrice(originalPrice);
    }
}

外部客户端

package strategy.examp02;
import java.math.BigDecimal;
//外部客户端
public class Client {
    public static void main(String[] args) {
        //1.创建老客户的报价策略,多态使用
        IQuoteStrategy oldQuoteStrategy = new OldCustomerQuoteStrategy();

        //2.创建报价上下文对象,并设置具体的报价策略
        QuoteContext quoteContext = new QuoteContext(oldQuoteStrategy);

        //3.调用报价上下文的方法
        BigDecimal price = quoteContext.getPrice(new BigDecimal(100));

        System.out.println("折扣价为:" +price);
    }
}

控制台输出:

恭喜!老客户享有9折优惠!
折扣价为:90.00

 

这个时候,商城营销部推出了新的客户类型--MVP,可以享受7折优惠,那该怎么办?

这个很容易,只需要新增个报价策略的实现,然后外部客户端调用的时候,创建这个新增的报价策略实现,并放到策略上下文就可以啦,对原本的已经实现的代码没有任何的改动。

MVP用户的报价策略实现:

package strategy.examp02;
import java.math.BigDecimal;
//MVP客户的报价策略实现
public class MVPCustomerQuoteStrategy implements IQuoteStrategy {
    @Override
    public BigDecimal getPrice(BigDecimal originalPrice) {
        System.out.println("哇偶!MVP客户享受7折优惠!!!");
        originalPrice = originalPrice.multiply(new BigDecimal(0.7)).setScale(2,BigDecimal.ROUND_HALF_UP);
        return originalPrice;
    }
}

外部客户端实现:

package strategy.examp02;
import java.math.BigDecimal;
//外部客户端
public class Client {
    public static void main(String[] args) {
        //创建MVP客户的报价策略
        IQuoteStrategy mvpQuoteStrategy = new MVPCustomerQuoteStrategy();

        //创建报价上下文对象,并设置具体的报价策略
        QuoteContext quoteContext = new QuoteContext(mvpQuoteStrategy);

        //调用报价上下文的方法
        BigDecimal price = quoteContext.getPrice(new BigDecimal(100));

        System.out.println("折扣价为:" +price);
    }
}

控制台输出

哇偶!MVP客户享受7折优惠!!!
折扣价为:70.00

 

深入理解策略模式

策略模式的作用:

把具体的算法实现从业务逻辑中剥离出来,成一系列的独立算法类,使得可以相互替换。

策略模式的重点:

不是如何设计是实现算法,而是如何组织和调用这些算法,从而让我们的程序结构更加的灵活、可扩展。

策略模式就是把各个平等的具体实现进行抽象化,封装成独立的算法,然后经过上下文和具体的算法类进行交互。各个算法之间都是平等的,地位都是一致的,正是由于各个算法的平等性,所以是可替代的。但是同一时刻只能使用一个策略。

三国刘备取西川时,庞统给的上、中、下的三个计策:

  上策:挑选精兵,昼夜兼行直接偷袭成都,可以一举而定,此为上计计也。

  中策:杨怀、高沛是蜀中名将,手下有精锐部队,而且据守关头,我们可以装作要回荆州,引他们轻骑来见,可就此将其擒杀,而后进兵成都,此为中计。

  下策:退还白帝,连引荆州,慢慢进图益州,此为下计。
  这三个计策都是取西川的计策,也就是攻取西川这个问题的具体的策略算法,刘备可以采用上策,可以采用中策,当然也可以采用下策,由此可见策略模式的各种具体的策略算法都是平等的,可以相互替换。

  那谁来选择具体采用哪种计策(算法)?

在这个故事中当然是刘备选择了,也就是外部的客户端选择使用某个具体的算法,然后把该算法(计策)设置到上下文当中;

还有一种情况,客户端不选择具体的算法,把这个事情交给上下文,这相当于刘备说我不管哪种计策,只要攻下西川即可,具体怎么攻占有上下文决定。

//攻取西川的策略
2 public interface IOccupationStrategyWestOfSiChuan {
3     public void occupationWestOfSiChuan(String msg);
4 }
//攻取西川的上上计策
public class UpperStrategy implements IOccupationStrategyWestOfSiChuan {
    @Override
    public void occupationWestOfSiChuan(String msg) {
        if (msg == null || msg.length() < 5) {
            //故意设置障碍,导致上上计策失败
            System.out.println("由于计划泄露,上上计策失败!");
            int i = 100/0;
        }
        System.out.println("挑选精兵,昼夜兼行直接偷袭成都,可以一举而定,此为上计计也!");
    }
}
//攻取西川的中计策
public class MiddleStrategy implements IOccupationStrategyWestOfSiChuan {
    @Override
    public void occupationWestOfSiChuan(String msg) {
        System.out.println("杨怀、高沛是蜀中名将,手下有精锐部队,而且据守关头,我们可以装作要回荆州,引他们轻骑来见,可就此将其擒杀,而后进兵成都,此为中计。");
    }
}
//攻取西川的下计策
public class LowerStrategy implements IOccupationStrategyWestOfSiChuan {
    @Override
    public void occupationWestOfSiChuan(String msg) {
        System.out.println("退还白帝,连引荆州,慢慢进图益州,此为下计。");
    }
}
//攻取西川参谋部,就是上下文啦,由上下文来选择具体的策略
 2 public class OccupationContext  {
 3 
 4     public void occupationWestOfSichuan(String msg){
 5         //先用上上计策
 6         IOccupationStrategyWestOfSiChuan strategy = new UpperStrategy();
 7         try {
 8             strategy.occupationWestOfSiChuan(msg);
 9         } catch (Exception e) {
10             //上上计策有问题行不通之后,用中计策
11             strategy = new MiddleStrategy();
12             strategy.occupationWestOfSiChuan(msg);
13         }
14     }
15 }
//此时外部客户端相当于刘备了,不管具体采用什么计策,只要结果(成功的攻下西川)
public class Client {

    public static void main(String[] args) {
        OccupationContext context = new  OccupationContext();
        //这个给手下的人激励不够啊
        context.occupationWestOfSichuan("拿下西川");
        System.out.println("=========================");
        //这个人人有赏,让士兵有动力啊
        context.occupationWestOfSichuan("拿下西川之后,人人有赏!");
    }
}

控制台输出

由于计划泄露,上上计策失败!
杨怀、高沛是蜀中名将,手下有精锐部队,而且据守关头,我们可以装作要回荆州,引他们轻骑来见,可就此将其擒杀,而后进兵成都,此为中计。
=========================
挑选精兵,昼夜兼行直接偷袭成都,可以一举而定,此为上计计也!

 

策略模式的优点:

  1. 策略模式的功能通过抽象、封装来定义一系列的算法,是的算法之间可以相互替换,所以为这些算法定义一个公共的接口,用来约束算法的功能实现。如果算法有公共的功能,可以将接口变为抽象类,将公共功能放在了抽象父类里面。
  2. 策略模式的算法是可以相互替换,平等的,避免了if else 组织结构。
  3. 扩展性更好:更容易扩展策略,只需要新增一个策略实现类,然后在使用策略实现的地方,使用策略实现即可。

策略模式的缺点:

  1. 增加了对象的数量。
  2. 只适合扁平的算法结构。由于策略模式是平等的关系,实际上就是扁平的算法结构。

策略模式的本质:

分离算法,选择实现。

策略模式体现了开闭原则:策略模式将一系列的算法进行封装,从而定义了良好的程序结构,在出现新的算法的时候,可以很容易的将新的算法实现加入到已有的系统中,而已有的实现不需要修改。

策略模式体现了里氏替换原则:策略模式是一个扁平的结构,各个策略都是兄弟关系,实现了同一个接口或者继承同一个抽象类。这样只需要实现策略的客户端保持面向抽象编程,就可以动态切换不同的策略实现进行替换。

 

以上就是策略模式的基本内容和使用,也是设计模式中另一个较为重要的模式,希望对大家有所帮助!!!

posted @ 2019-11-10 14:50  国孩  阅读(226)  评论(0编辑  收藏  举报