zoukankan      html  css  js  c++  java
  • 策略模式-设计模式

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

    现实生活中我们去商场上买东西的时候,卖场经常根据不同的客户来制定不同的报价策略,比如新客户不打折扣,针对老客户打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. 只适合扁平的算法结构。由于策略模式是平等的关系,实际上就是扁平的算法结构。

    策略模式的本质:

    分离算法,选择实现。

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

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

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

  • 相关阅读:
    IBoutlet 学习笔记
    Stirng str1 = new String("abc");Stirng str2 = new String("abc");创建了几个对象
    readonly strong nonatomic 和IBOutlet
    MAC技巧,让mac和windows之间识别硬盘
    Eclipse Retina 视网膜屏 设置
    PList
    三种方法更改MAC OS X下的HOSTS文件
    第一个Dart程序HelloDart
    第三个Dart程序扩展(访问lib文件里的成员变量)
    初识Dart
  • 原文地址:https://www.cnblogs.com/guohai-stronger/p/11829428.html
Copyright © 2011-2022 走看看