设计模式 --- 策略模式

Strategy --- 策略模式:

模式动机:
在软件开发中,常遇到类似问题,实现某一个功能的途径有很多,如查找,排序等,一种常用的方法是"硬编码"(Hard Coding)在一个类中,如果需要提供多种查找算法,可以将这些算法写到一个类中,在该类中提供多个方法,每一方法对应一个具体的查找算法;当然也可以将这些查找算法封装在一个统一的方法中,用过if...else... 等条件判断语句来进行选择。这两种实现方法我们都可以称之为硬编码,如果要新增一种新的查找算法,需要修改封装算法类的源代码;更换查找算法,也需要修改客户端调用代码。在这个算法类中封装了大量的查找算法,该类代码将教负责,维护教为困难。
为了解决这些问题,可以定义一些独立的类来封装不同的算法,每一个类封装一个具体的算法,在这里,每一个封装算法的类我们都可以称之为策略(Strategy),为了保证这些策略的一致性,一般会用一个抽象的策略类来做算法的定义,而具体每种算法则对应于一个具体的策略类。

模式定义:
策略模式: 属于对象的行为模式。其用意是针对一组算法,将每一个算法封装到具有公公接口的独立的类中,从而使得它们可以相互替换。

策略模式:使得算法可以在不影响到客户端的情况下发生变化。

策略模式: 是对算法的包装, 是把使用算法的责任和算法本身分割开来,委派给不同的对象管理。策略模式通常把一个系列的算法包装到一系列的策略类里面,作为一个抽象策略类的子类。用一句话来说, 就是:"准备一组算法,并将每一个算法封装起来,使得它们可以互换" 。

模式结构:

Context: 环境角色,持有一个Strategy的引用。
Strategy: 抽象策略角色,这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
ConreteStrategy: 具体策略类,保证了相关的算法或行为。

环境角色类:
public class Context {

  // 持有一个具体策略的对象
  private Strategy strategy;

  /**
   * 构造函数, 传入一个具体策略对象
   *
   * @params strategy  具体策略对象
   */
  public Context(Strategy strategy) {
    this.strategy = strategy;
  }

  /**
   * 策略方法
   */
  public void contextInterface() {
    strategy.strategyInterface();
  }
}

抽象策略类:
public interface Strategy {
  /**
   * 策略方法
   */
  void strategyInterface();
}

具体策略类:
public class ConcreteStrategyA implements Strategy {

  @Override
  public void strategyInterface() {
    // 相关业务
  }
}

public class ConcreteStrategyB implements Strategy {

  @Override
  public void strategyInterface() {
    // 相关业务
  }
}

public class ConcreteStrategyC implements Strategy {

  @Override
  public void strategyInterface() {
    // 相关业务
  }
}

实例: 收银软件项目

需求: 收银员根据客户购买产品的单价和数量,想客户打印小票。

简单实现:

public class Cash {

  public String list = "";
  public Double totalPrice = 0.00;

  public void buttonOk() {
    Scanner scanner = new Scanner(System.in);
    System.out.println("输入单价:");
    String price = scanner.nextLine();

    scanner = new Scanner(System.in);
    System.out.println("输入数量:");
    String num = scanner.nextLine();

    scanner = new Scanner(System.in);
    System.out.println("输入折扣:");
    String discount = scanner.nextLine();

    Double account = Double.parseDouble(price) * Integer.parseInt(num) * Double.parseDouble(discount) / 10;
    list += "单价:" + price + ", 数据:" + num + ", 折扣:" + discount + "\n";
    totalPrice += account;
  }

  public static void main(String[] args) {

    Cash cash = new Cash();
    boolean flag = true;
    while (flag) {
      cash.buttonOk();
      if (cash.totalPrice > 20) {
        flag = false;
      }
    }

    System.out.println("=============");
    System.out.println("清单:\n" + cash.list);
    System.out.println("总价:" + cash.totalPrice);
  }
}

缺点:
面相对象的角度思考,这个类将前端输入和业务逻辑混合在一块了,不利于维护, 扩展,复用,也不灵活。
如果之后要扩展,增加新的活动,则每次都需要修改原来的代码。


使用简单的工厂模式实现:
首先需要一个工厂类,可以实现,根据不同的类型,返回不同的现金收费方式。

/**
 * 现金收费方式类
 */
public abstract class CashFee {
  public abstract double acceptCash(double money);
}

// 定义现金收费方式: 正常收费, 折扣, 满减

/**
 * 正常收费
 */
public class NormalCashFee extends CashFee {

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

/**
 * 折扣
 */
public class DiscountCashFee extends CashFee {

  private double discount = 0.00;

  public DiscountCashFee(double discount) {
    this.discount = discount / 10;
  }

  @Override
  public double acceptCash(double money) {
    return this.discount * money;
  }

  public double geDiscount() {
    return discount;
  }

  public void setDiscount(double discount) {
    this.discount = discount;
  }
}

/**
 * 满减
 */
public class ReturnCashFee extends CashFee {

  private double baseCash;
  private double returnCash;

  public ReturnCashFee(double baseCash, double returnCash) {
    this.baseCash = baseCash;
    this.returnCash = returnCash;
  }

  public  void setBaseCash(double baseCash) {
    this.baseCash = baseCash;
  }

  public double getBaseCash() {
    return this,baseCash;
  }

  public void setReturnCash(double returnCash) {
    this.returnCash = returnCash;
  }

  public double getReturnCash() {
    return this.returnCash;
  }

  @Override
  public double acceptCash(double money) {
    return money - Math.floor(money / baseCash) * returnCash;
  }
}

// 定义工厂类,用于生产各种各样的现金收费方式

public class CashFeeFactory {

  public static CashFee createCashFee(int type, double discount, double baseCash, double returnCash) {

    CashFee cashFee = null;
    switch (type) {
      case 1:
        cashFee = new NormalCashFee();
        break;
      case 2:
        cashFee = new DiscountCashFee(discount);
        break;
      case 3:
        cashFee = new ReturnCashFee(baseCash, returnCash);
        break;
      default:
        break;
    }

    return cashFee;
  }

  public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);
    System.out.println("输入单价:");
    String price = scanner.nextLine();

    scanner = new Scanner(System.in);
    System.out.println("输入数量:");
    String num = scanner.nextLine();

    scanner = new Scanner(System.in);
    System.out.println("输入折扣类型(1 无折扣 2 打折 3 满减):");
    String type = scanner.nextLine();

    double discount = 0.0d;
    double basePrice = 0;
    double returnPrice = 0;
    if ("2".equals(type)) {
        scanner = new Scanner(System.in);
        System.out.println("输入折扣:");
        discount = Double.parseDouble(scanner.nextLine());
    }

    if ("3".equals(type)) {
        scanner = new Scanner(System.in);
        System.out.println("基础金额:");
        basePrice = Double.parseDouble(scanner.nextLine());
        scanner = new Scanner(System.in);
        System.out.println("返还现金:");
        returnPrice = Double.parseDouble(scanner.nextLine());
    }

    Double money = Double.parseDouble(price) * Integer.parseInt(num);
    CashFee cashFee = CashFeeFactory.createCashFee(Integer.parseInt(type), discount, basePrice, returnPrice);
    System.out.println("总价:" + cashFee.acceptCash(money));
  }
}

简单工厂模式的优缺点:
优点:
1. 业务逻辑和前端展示分离开了。业务逻辑的修改,不影响前端代码的展示。
2. 每一个业务逻辑单独一个雷,修改或者添加一个类,不会影响到其他的类。
3. 使用工厂类封装了业务逻辑类,前端不需要知道到底每种业务怎么实现,只需要知道他的父类即可。

缺点:
1. 如果活动很频繁,经常会搞各种各样的活动,那么业务逻辑就会有很多种,每次都要增加一个类。

2. 每增加一个类都要丢改工厂类,修改很频繁。将简单工厂模式和策略模式结合起来:

// 增加 一个上下文

public class CashContext {

  private CashFee cashFee;

  /**
   * 构建不同的收费模式
   * @param type       [description]
   * @param discount   [description]
   * @param baseCash   [description]
   * @param returnCash [description]
   */
  public CashContext(int type, double discount, double baseCash, double returnCash) {
    switch(type) {
      case 1: {
        this.cashFee = new NormalCashFee();
        break:
      }
      case 2: {
        this.cashFee = new DiscountCashFee(discount);
        break;
      }
      case 3: {
        this.cashFee = new ReturnCashFee(baseCash, returnCash);
        break;
      }
      default:
        break;
    }
  }

  /**
   * 获取结果
   */
  public double getResult(double money) {
    return cashFee.acceptCash(money);
  }

/**
 * 测试类
 * @param args [description]
 */
  public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("输入单价:");
        String price = scanner.nextLine();

        scanner = new Scanner(System.in);
        System.out.println("输入数量:");
        String num = scanner.nextLine();

        scanner = new Scanner(System.in);
        System.out.println("输入折扣类型(1 无折扣 2 打折 3 满减):");
        String type = scanner.nextLine();

        double discount = 0.0d;
        double basePrice = 0;
        double returnPrice = 0;
        if ("2".equals(type)) {
            scanner = new Scanner(System.in);
            System.out.println("输入折扣:");
            discount = Double.parseDouble(scanner.nextLine());
        }

        if ("3".equals(type)) {
            scanner = new Scanner(System.in);
            System.out.println("基础金额:");
            basePrice = Double.parseDouble(scanner.nextLine());
            scanner = new Scanner(System.in);
            System.out.println("返还现金:");
            returnPrice = Double.parseDouble(scanner.nextLine());
        }

        Double money = Double.parseDouble(price) * Integer.parseInt(num);

        CashContext cashContext = new CashContext(Integer.parseInt(type), discount, basePrice, returnPrice);


        System.out.println("总价:" + cashContext.getResult(money));
    }
}


对比简单工厂设计模式 和 策略模式+工厂模式的区别:

CashFee cashFee = CashFeeFactory. createCashFee(Integer.parseInt(type), discount, baseCash, returnCash);

CashContext cashContext = new CashContext(Integer.parseInt(type), discount, baseCash, returnCash);

对于客户端而言, 简单工厂设计模式,客户端要知道两个类, CashFee 和 CashFeeFactory, 而 策略模式 + 简单工厂模式,客户端只需要知道 CashContext 类即可, 降低了耦合性。


总结策略模式:
策略模式: 分别封装行为接口,实现算法族,超类里放行为接口对象,在子类里具体设定行为对象。  原则就是:分离变化部分,封装接口, 基于接口变成各种功能。此模式让行为算法的变化独立于算法的使用者。

策略模式应用分析:

策略模式分析:
策略模式是对算法的封装,他把算法的责任和算法本身分割开,委派给不同地对象管理。策略模式通常把一个系列的算法封装到一系列的策略类里面,作为一个抽象策略类的子类。一句话说"转变一组算法,并将每一个算法封装起来,使得它们可以互换"。

重心:
不是如何实现算法,而是如何组织,调用这些算法,从而让程序结构更灵活,具有更好的维护性和扩展性。

注意点:
1. 分析项目中变化部分与不变部分
2. 多用组合少用继承;用行为类组合,而不是行为的继承。更有弹性。
 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

杨愁心

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值