设计模式轻松学【九】策略模式

简介: 生活中的策略- 假如现在你要出行旅游,那么出行方式有--飞机、自驾游、火车等,这几种方式就是策略。- 再比如:某大型商场搞活动--满100元送杯子,满300减50,满1000元抽奖等,这种活动也是策略。- 在游戏中,我们打一个普通的怪使用普通的招即可,打大 BOSS 就要是用大招,这也是一种策略。

背景

现在我们要做一个折扣管理的模块,简要点就是要针对不同的客户,提供不同的折扣报价。针对新客户不打折扣,针对老客户打9折,针对VIP客户打8折。其实这非常简单,我们几分钟就能写好这个程序。

package com.it235.strategy;

import java.math.BigDecimal;
/**
 * 特价折扣管理 
 * 设计模式轻松学Java:君哥聊编程
 *
 */
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;
    }
}

上面的代码工作的很好,用得很愉快,可是上面的代码是有问题的。上面存在的问题:把不同客户的报价的算法都放在了同一个方法里面,如果条件过多,会出现非常多的ifelse,程序比较臃肿。

所以我们需要对代码进行优化,优化后的代码如下。

package com.it235.strategy;

import java.math.BigDecimal;

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

上面的代码比刚开始的时候要好一点,它把每个具体的算法都单独抽出来作为一个方法,当某一个具体的算法有了变动的时候,只需要修改响应的报价算法就可以了。

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

  1. 当我们新增一个客户类型的时候,首先要添加一个该种客户类型的报价算法方法,然后再quote方法中再加一个else if的分支,是不是感觉很是麻烦呢?而且这也违反了设计原则之一的开闭原则(open-closed-principle)
  2. 我们经常会面临这样的情况,不同的时期使用不同的报价规则,比如在各个节假日举行的各种促销活动时、商场店庆时往往都有普遍的折扣,但是促销时间一旦过去,报价就要回到正常价格上来。按照上面的代码我们就得修改if else里面的代码很是麻烦

那有没有什么办法使得我们的报价管理即可扩展、可维护,又可以方便的响应变化呢?当然有解决方案啦,就是我们下面要讲的策略模式。

定义与特点

  • 策略定义

    策略是对算法的封装,是一种行为模式,将每一个算法封装到具有共同接口的独立的类中,使算法本身和使用算法的客户端分割开来,相互独立,从而使得它们可以相互替换。

  • 策略的特点

    • 测试可减少大量的if-else语句,可自动切换不同的实现
    • 扩展性强,添加策略无非就是添加一个具体的实现类而已,代价非常低
  • 参与角色

    角色 类别 说明
    Strategy 抽象的策略 是一个接口或抽象类
    ConcreteStrategy 具体的策略类 实现了抽象的策略
    Context 一个普通的类 上下文环境,持有 Stragegy 的引用
  • 策略模式简单的 UML

    image.png

  • UML代码示例

    • 声明策略接口

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

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

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

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

模式实现分析

我们继续针对上述的折扣管理来进行改进,我们可以应用策略模式对其进行改造,不同类型的客户有不同的折扣,我们可以将不同类型的客户的报价规则都封装为一个独立的算法,然后抽象出这些报价算法的公共接口

  • 公共折扣策略接口

    import java.math.BigDecimal;
    //报价策略接口
    public interface IQuoteStrategy {
        //获取折后价的价格
        BigDecimal getPrice(BigDecimal originalPrice);
    }
  • 新客户报价策略实现

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

    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客户报价策略实现

    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;
        }
    }
  • 报价上下文

    
    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);
        }
    }
  • 外部客户端调用

    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用户(Most Valuable Person),可以享受折扣7折优惠,那该怎么办呢?

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

    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;
        }
    }
  • 外部调用客户端

    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
  • 注意点

    策略的核心不是如何实现算法,而是如何更优雅的把这些算法组织起来,让客户端非常好调用「虽然策略非常多,可以自由切换,但是同一时间客户端只能调用一个策略,其实也很好理解,你不可能同时既坐飞机,又坐火车」。

策略模式和简单工厂模式

策略模式和简单工厂非常相似,结构基本上一样,但是它们侧重点不一样

  • 策略模式:是一个行为模式,解决策略的切换和扩展,让策略独立于客户端
  • 简单工厂模式:是一种创建模式「创建对象」,接收指令创建出具体的对象,让对象的创建和具体的使用客户无关

但是我们在策略模式中可以使用简单工厂模式「把生成策略这一过程使用工厂去实现,这样好不好呢?适合就是最好的」

总结

  • 优点

    1. 结构清晰,把策略分离成一个个单独的类「替换了传统的 if else」
    2. 代码耦合度降低,安全性提高「各个策略的细节被屏蔽」
    3. 扩展性更好:在策略模式中扩展策略实现非常的容易,只要新增一个策略实现类,然后使用即可。
  • 缺点

    1. 客户端必须要知道所有的策略类,否则你不知道该使用那个策略,所以策略模式适用于提前知道所有策略的情况下
    2. 增加了对象的数量,如果可选的策略有很多的话,那对象的数量也会很多
    3. 只适合偏平的算法结构,策略均属于同一等级,构成扁平的结构,这就限制了算法的使用层级,且不能被嵌套。
目录
相关文章
|
2月前
|
设计模式 算法 测试技术
PHP中的设计模式:策略模式的应用与实践
在软件开发的浩瀚海洋中,设计模式如同灯塔,指引着开发者们避开重复造轮子的暗礁,驶向高效、可维护的代码彼岸。今天,我们将聚焦于PHP领域中的一种重要设计模式——策略模式,探讨其原理、应用及最佳实践,揭示如何通过策略模式赋予PHP应用灵活多变的业务逻辑处理能力,让代码之美在策略的变换中熠熠生辉。
|
13天前
|
设计模式 算法 Kotlin
Kotlin教程笔记(53) - 改良设计模式 - 策略模式
Kotlin教程笔记(53) - 改良设计模式 - 策略模式
39 2
|
1月前
|
设计模式 算法 Kotlin
Kotlin教程笔记(53) - 改良设计模式 - 策略模式
本教程详细讲解Kotlin语法,适合深入学习。快速入门可参考“简洁”系列教程。本文通过游泳运动员的案例,介绍策略模式及其在Kotlin中的改良应用,利用高阶函数简化代码结构,提高灵活性。
30 3
|
1月前
|
设计模式 算法 Kotlin
Kotlin教程笔记(53) - 改良设计模式 - 策略模式
本教程详细讲解Kotlin语法,适合深入学习。快速入门可参考“简洁”系列教程。本文介绍策略模式在Kotlin中的应用,通过游泳运动员的例子,展示如何使用接口和高阶函数实现策略模式,使代码更简洁、灵活。
28 2
|
1月前
|
设计模式 算法 Kotlin
Kotlin教程笔记(53) - 改良设计模式 - 策略模式
Kotlin教程笔记(53) - 改良设计模式 - 策略模式
62 3
|
1月前
|
设计模式 算法 Kotlin
Kotlin教程笔记(53) - 改良设计模式 - 策略模式
Kotlin教程笔记(53) - 改良设计模式 - 策略模式
27 3
|
1月前
|
设计模式 算法 PHP
PHP中的设计模式:策略模式的深入解析与实践
【10月更文挑战第9天】 策略模式是一种行为设计模式,它允许在运行时选择算法的行为。在PHP开发中,通过使用策略模式,我们可以轻松切换算法或逻辑处理方式而无需修改现有代码结构。本文将深入探讨策略模式的定义、结构以及如何在PHP中实现该模式,并通过实际案例展示其应用价值和优势。
30 1
|
1月前
|
设计模式 算法 PHP
PHP中的设计模式:策略模式的深入解析与应用
【10月更文挑战第8天】 在软件开发的浩瀚宇宙中,设计模式如同星辰指引,照亮了代码设计与架构的航道。本文旨在深入探索PHP语境下策略模式(Strategy Pattern)的精髓,不仅剖析其内核原理,还将其融入实战演练,让理论在实践中生根发芽。策略模式,作为解决“如何优雅地封装算法族”的答案,以其独特的灵活性与扩展性,赋予PHP应用以动态变换行为的能力,而无需牵动既有的类结构。
24 2
|
1月前
|
设计模式 算法 PHP
PHP中的设计模式:策略模式的深入解析与实践
在PHP开发中,设计模式是提高代码可读性、可维护性和扩展性的重要工具。本文将深入探讨策略模式这一行为型设计模式,通过分析其定义、结构、使用场景以及在PHP中的实际应用,帮助开发者更好地理解和运用策略模式来优化自己的项目。不同于传统摘要的简洁概述,本文摘要部分将详细阐述策略模式的核心理念和在PHP中的实现方法,为读者提供清晰的指引。
|
1月前
|
设计模式 算法 PHP
PHP中的设计模式:策略模式的深入解析与实践
策略模式是一种行为型设计模式,用于定义一系列算法,将每种算法都封装起来,并使它们可以互换。本文将探讨如何在PHP中实现策略模式,以及如何利用它来提高代码的灵活性和可维护性。通过具体示例,我们将看到策略模式在处理复杂业务逻辑时的优势,从而帮助开发者编写出更加清晰、易于扩展的代码。