最有深度的策略模式解析

在生活中我们在做商品促销时,会针对不同的客户有多同的折扣价格,新客户不打折,老客户打九折,vip打八折等等。这就要我们针对不同的客户有不同的定价策略。

所以我会可能会有如下的代码

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;
    }

}

上面的带很容易理解,写起来也非常简单,但是当针对的客户的群体多样化以后,代码放在一个方法中以后将会变的非常臃肿,且难以维护。

下面我们可以做一个简单的改善

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;     }
 
 }

这次我们将具体的定价策略给抽了出来,这样当某类客户的定价策略发生改变时,我们只需要找到对应的方法进行修改就可以了。

咋一看代码已经有点完美了,可以当我们增加一种客户类型的时候是不是还要在if-else 在添加一个分支?这样显然非常麻烦,而且违反设计模式的开闭原则:对模块扩展开放,对修改模块的源代码关闭。因为在实际的项目中修改源码可能带来不可预知的后果。

定义:

策略模式:定义一系列算法,在分别分别封装各个算法,而这些算法可以相互替代。

结构:

  1. 策略接口Strategy:用来统一管理算法策略。
  2. 具体算法策略ConcreteStrategy,具体的策略实现,即具体的算法实现。
  3. 策略上下文StrategyContext,可理解为为策略的使用者。

策略模式的通用实现。

策略具体实现A

// 具体的策略实现
  public class ConcreteStrategyA implements Strategy {
     //具体的算法实现
      @Override
      public void algorithmMethod() {
          System.out.println("this is ConcreteStrategyA method...");
      }
 }

策略具体实现B

// 具体的策略实现
  public class ConcreteStrategyB implements Strategy {
     //具体的算法实现
      @Override
      public void algorithmMethod() {
          System.out.println("this is ConcreteStrategyB method...");
      }
 }

策略上下文

/**
  * 策略上下文
   */
  public class StrategyContext {
      //持有一个策略实现的引用
      private Strategy strategy;
      //使用构造器注入具体的策略类
     public StrategyContext(Strategy strategy) {
         this.strategy = strategy;
     }
 
     public void contextMethod(){
         //调用策略实现的方法
         strategy.algorithmMethod();
     }
 }

测试

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

策略模式的作用:

  1. 把一系列组合中的算法抽离出来,封装成单个算法,在StrategyContext定义策略接口,可实现多态,灵活扩展。
  2. 策略模式的核心时灵活的组织调用算法,降低代码维护成本。

策略与上下文的关系

在策略模式中,一般情况下都是策略上下文持有策略的引用以便于执行具体的策略,但具体的策略也可以包含上下文对象,以便从上下文中获取数据,具体策略通过回调上下文方法获取数据。

下面我们演示这种情况:

  在跨国公司中,一般都会在各个国家和地区设置分支机构,聘用当地人为员工,这样就有这样一个需要:每月发工资的时候,中国国籍的员工要发人民币,美国国籍的员工要发美元,英国国籍的要发英镑。

支付接口策略

 //支付策略接口
 public interface PayStrategy {
     //在支付策略接口的支付方法中含有支付上下文作为参数,以便在具体的支付策略中回调上下文中的方法获取数据
     public void pay(PayContext ctx);

 }

美金支付策略

 //美金支付策略
 public class DollarPay implements PayStrategy {
    @Override
     public void pay(PayContext ctx) {
         System.out.println("现在给:"+ctx.getUsername()+" 美金支付 "+ctx.getMoney()+"dollar !");
     }
 }

人民币支付策略

 //人民币支付策略
 public class RMBPay implements PayStrategy {
     @Override
     public void pay(PayContext ctx) {
         System.out.println("现在给:"+ctx.getUsername()+" 人民币支付 "+ctx.getMoney()+"元!");
     }
 }

支付上下文

//支付上下文,含有多个算法的公有数据
  public class PayContext {
      //员工姓名
     private String username;
     //员工的工资
     private double money;
      //支付策略
     private PayStrategy payStrategy;
 
    public void pay(){
        //调用具体的支付策略来进行支付
         payStrategy.pay(this);
     }
 
     public PayContext(String username, double money, PayStrategy payStrategy) {
         this.username = username;
         this.money = money;
         this.payStrategy = payStrategy;
     }
 
     public String getUsername() {
         return username;
     }
 
     public double getMoney() {
         return money;
     }
 }

客户端调用

//外部客户端
  public class Client {
      public static void main(String[] args) {
          //创建具体的支付策略
          PayStrategy rmbStrategy = new RMBPay();
          PayStrategy dollarStrategy = new DollarPay();
          //准备小王的支付上下文
          PayContext ctx = new PayContext("小王",30000,rmbStrategy);
          //向小王支付工资
         ctx.pay();
 
         //准备Jack的支付上下文
         ctx = new PayContext("jack",10000,dollarStrategy);
         //向Jack支付工资
         ctx.pay();
     }
 }

那么现在要新增一个银行账户的支付策略应该怎么办呢?

根据开闭原则,我们不能对原有的代码进行修改,而且鉴于原有的上下文中需要有银行账户的需求,所以我们必须新增一个支付策略,并且扩展出一个带我支付账户的上下文,所以我们可以通过子类继承来实现。

银行账户支付策略

 //银行账户支付
 public class AccountPay implements PayStrategy {
     @Override
     public void pay(PayContext ctx) {
         PayContextWithAccount ctxAccount = (PayContextWithAccount) ctx;
         System.out.println("现在给:"+ctxAccount.getUsername()+"的账户:"+ctxAccount.getAccount()+" 支付工资:"+ctxAccount.getMoney()+" 元!");
     }
 }

带我银行账户的支付上下文

//带银行账户的支付上下文
  public class PayContextWithAccount extends PayContext {
      //银行账户
      private String account;
      public PayContextWithAccount(String username, double money, PayStrategy payStrategy,String account) {
          super(username, money, payStrategy);
          this.account = account;
      }
  
     public String getAccount() {
         return account;
     }
 }

测试客户端

 //外部客户端
  public class Client {
      public static void main(String[] args) {
          //创建具体的支付策略
          PayStrategy rmbStrategy = new RMBPay();
          PayStrategy dollarStrategy = new DollarPay();
          //准备小王的支付上下文
          PayContext ctx = new PayContext("小王",30000,rmbStrategy);
          //向小王支付工资
         ctx.pay();
         //准备Jack的支付上下文
         ctx = new PayContext("jack",10000,dollarStrategy);
         //向Jack支付工资
         ctx.pay();
         //创建支付到银行账户的支付策略
         PayStrategy accountStrategy = new AccountPay();
         //准备带有银行账户的上下文
         ctx = new PayContextWithAccount("小张",40000,accountStrategy,"1234567890");
         //向小张的账户支付
         ctx.pay();
     }
 }

上面是从策略的上下文中获取数据,我们还可以在策略实现中添账户,直接获取数据。

 //支付到银行账户的策略
  public class AccountPay2 implements PayStrategy {
      //银行账户
      private String account;
      public AccountPay2(String account) {
          this.account = account;
      }
      @Override
      public void pay(PayContext ctx) {
         System.out.println("现在给:"+ctx.getUsername()+"的账户:"+getAccount()+" 支付工资:"+ctx.getMoney()+" 元!");
     }
     public String getAccount() {
         return account;
     }
     public void setAccount(String account) {
         this.account = account;
     }
 }

测试客户端

//外部客户端
  public class Client {
      public static void main(String[] args) {
          //创建具体的支付策略
          PayStrategy rmbStrategy = new RMBPay();
          PayStrategy dollarStrategy = new DollarPay();
          //准备小王的支付上下文
          PayContext ctx = new PayContext("小王",30000,rmbStrategy);
          //向小王支付工资
         ctx.pay();
         //准备Jack的支付上下文
         ctx = new PayContext("jack",10000,dollarStrategy);
         //向Jack支付工资
         ctx.pay();
         //创建支付到银行账户的支付策略
         PayStrategy accountStrategy = new AccountPay2("1234567890");
         //准备上下文
         ctx = new PayContext("小张",40000,accountStrategy);
         //向小张的账户支付
         ctx.pay();
     }
 }

两种实现的比较

扩展上下文的实现:

      优点:具体的策略实现风格非常统一,策略实现所需要的数据都是从上下文中直接获取,而且这数据是其他策略实现可以共享的。

     缺点:某些数据的存在只是服务特定策略场景,大部分策略实现并不需要,会有数据浪费之嫌,而且如果每次都直接上下文中扩展算法数据,长期来看会使上下文层次结构复杂,难以维护。

具体策略中扩展数据:

    优点:简单,且非常容易实现。

    缺点:会使整体的策略实现风格不一致,一部分数据来自上下文中,一部分数据来自策略实现本身,外部在使用策略实现的时候和其他策略实现不一致,对于动态的切换策略不方便。

 

策略模式在JDK中的应用:

    在多线程编程中,我们经常使用线程池来管理线程,减少线程的频繁地创建和销毁,消耗系统资源。在创建线程池的时候,我们经常使用Executor工厂类来创建线程池,实际上Executors的是使用ThreadPoolExecutor,它又一个构造函数。

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

corePoolSize:线程池中核心线程数量,即使这些线程没有任务干,也会存在。

maximumPoolSize:线程池中最多能创建的线程数量。

keepAliveTime:当线程池中线程数量大于corePoolSize时,多余的线程等待任务的最长时间。

unit:keepAliveTime的时间单位
workQueue:线程池中的还没有来得及执行任务保存在队列中的线程。

ThreadFactory:创建线程池中线程的工程

handler:当线程池中没有多余的线程来执行任务,队列中也排满,对仍在提交给线程池的任务的处理策略。

RejectedExecutionHandler:是一个策略接口,用在当前线程池中已经没有多余的线程,并且保存任务的多列也已经满了,对仍在提交给线程池的任务的处理策略。

public interface RejectedExecutionHandler {

    /**
     *当ThreadPoolExecutor的execut方法调用时,并且ThreadPoolExecutor不能接受一个任务Task时,该方法就有可能被调用。
   * 不能接受一个任务Task的原因:有可能是没有多余的线程来处理,有可能是workqueue队列中没有多余的位置来存放该任务,该方法有可能抛出一个未受检的异常RejectedExecutionException
     * @param r the runnable task requested to be executed
     * @param executor the executor attempting to execute this task
     * @throws RejectedExecutionException if there is no remedy
     */
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

该策略接口主要有四个在ThreadPoolExecutor类中实现的静态类部类。

AbortPolicy:该策略直接将提交的任务抛弃掉,并抛出RejectedExecutionException异常

 

/**
     * A handler for rejected tasks that throws a
     * {@code RejectedExecutionException}.
     */
    public static class AbortPolicy implements RejectedExecutionHandler {
        /**
         * Creates an {@code AbortPolicy}.
         */
        public AbortPolicy() { }

        /**
         * Always throws RejectedExecutionException.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         * @throws RejectedExecutionException always
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        }
    }

DiscardPolicy:该策略也是将提交策略抛弃掉,不管不问,但是不抛出异常

public static class DiscardPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code DiscardPolicy}.
         */
        public DiscardPolicy() { }

        /**
         * Does nothing, which has the effect of discarding task r.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        }
    }

DiscardIOldestPolicy:当执行器未关闭时,从任务队列中取出第一任务并抛弃掉,腾出空间给刚刚提交的任务。因为它会抛弃掉之前的任务,所以慎重使用。

/**
     * A handler for rejected tasks that discards the oldest unhandled
     * request and then retries {@code execute}, unless the executor
     * is shut down, in which case the task is discarded.
     */
    public static class DiscardOldestPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code DiscardOldestPolicy} for the given executor.
         */
        public DiscardOldestPolicy() { }

        /**
         * Obtains and ignores the next task that the executor
         * would otherwise execute, if one is immediately available,
         * and then retries execution of task r, unless the executor
         * is shut down, in which case task r is instead discarded.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                e.getQueue().poll();
                e.execute(r);
            }
        }
    }

CallerRunsPolicy:该策略不会抛弃任何任务,因为当前线程池中已经没有多余的线程来执行任务,该策略是在当前的线程中直接调用。

/**
     * A handler for rejected tasks that runs the rejected task
     * directly in the calling thread of the {@code execute} method,
     * unless the executor has been shut down, in which case the task
     * is discarded.
     */
    public static class CallerRunsPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code CallerRunsPolicy}.
         */
        public CallerRunsPolicy() { }

        /**
         * Executes task r in the caller's thread, unless the executor
         * has been shut down, in which case the task is discarded.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
        }
    }

类ThreadPoolExecutor中持有一个RejectedExecutionHandler的接口引用,以便可以在外部直接通过构造函数进行策略注入。

策略模式的优点

  1. 策略模式的功能就是将一组算法中的各个算法抽出共性的方法,再根据各个算法的特点封装自己特有的属性,如果有公共的属性可以封装为抽象类给子类继承。
  2. 策略模式的一系列算法是可以相互替换的,是平等的,写在一起就是if-else的结构
  3. 扩展性比较好,直接实现接口就可以直接替换使用。

策略模式的缺点

  1. 使用者必须了解所有的策略,清楚他们的不同
  2. 增加了对象的数量,因为策略模式是将每一个算法都单独的封装成具体的策略类,如果可选的策略很多,数量也会很多。
  3. 扁平的算法结构模式:各个算法的地位是平等是兄弟关系,限制了算法的使用层级不能嵌套。

策略模式的本质:分离算法,按需实现

如果仔细考虑策略模式的结构和功能的话,会发现没有上下文,我们就回到了面向接口的编程习惯,统一的策略接口和各个具体的算法策略实现,好像和上下文没啥关系,但是当我们用客户端进行操作的时候,客户端会直接与策略接口进行交互,当涉及到一些公共数据和方法的使用的时候会有大量的冗余数据,和复杂的操作的实现,而有了上下,这些操作就可以交给上下文来与客户端进行交互。

策略模式体现了设计的模式开闭原则和里氏替换原则。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值