吴志勇的博客 吴志勇的博客
  • h5

    • HTML5&CSS3
  • scss

    • css预处理语言
  • JavaScript

    • JavaScript教程
    • Ajax
    • ES6教程
    • NodeJS
    • Typescript
  • 框架

    • Jquery
    • VUE
    • React
  • Swing专题
  • java基础
  • javaweb
  • 框架
  • 数据库
  • netty
  • 设计模式
  • 微服务及架构
  • 云原生
  • maven
  • 单元测试
工具
我的
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

吴志勇

......
  • h5

    • HTML5&CSS3
  • scss

    • css预处理语言
  • JavaScript

    • JavaScript教程
    • Ajax
    • ES6教程
    • NodeJS
    • Typescript
  • 框架

    • Jquery
    • VUE
    • React
  • Swing专题
  • java基础
  • javaweb
  • 框架
  • 数据库
  • netty
  • 设计模式
  • 微服务及架构
  • 云原生
  • maven
  • 单元测试
工具
我的
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Swing专题

  • java基础

  • javaweb

  • 框架

  • Maven
  • 单元测试
  • 动态代理
  • 数据库

  • netty

  • 设计模式

    • simple_factory
    • factory_method
    • abstract_factory
    • singleton
    • builder
    • prototype
    • adapter
    • decorator
    • facade
    • bridge
    • proxy
    • composite
    • flyweight
    • observer
    • command
    • state
    • chain_of_responsibility
    • template_method
    • strategy
      • 问题引入
        • 问题描述
        • 模式定义
        • 问题分析
      • 模式实现
        • 解决方案
        • 代码实现
        • 结构组成
      • 模式评价
        • 适用场景
        • 实际应用
        • 优点缺点
      • 参考资料
    • interpreter
    • mediator
    • visitor
    • memento
    • iterator
  • 微服务及架构

  • 云原生

  • Velocity模板引擎
  • 后端
  • 设计模式
wuzhiyong
2025-01-13

strategy

# 策略模式

# 问题引入

# 问题描述

大鸟给小菜出了个作业,要求实现一个商场收银软件程序,营业员可以通过输入客户所有购买商品的单价和数量,程序自动计算出总金额。同时,商场有时会有打折活动(如商品打7折),或满减促销活动(如商品满300-100),程序应能考虑这些活动的情形,在尽量减少重复代码的前提下,实现正确的金额计算。

考虑到商场可能经常性地更改打折额度和返利额度,上述问题虽然可以用简单工厂模式实现,但如果每次扩展收费形式都要改动工厂,导致代码重新编译部署,则确实不是最好的处理方式。此时可以考虑使用策略模式。

# 模式定义

策略模式(Strategy Pattern)是指定义一个算法家族,使得家族内的不同算法都遵从算法家族的接口及方法规范,从而可以实现算法间互相替换,且不会影响到使用算法的客户。

# 问题分析

策略模式可以用于这一问题,因为商场促销活动变化繁多,如打7折,打8折,满300-100,满700-200等,这些促销活动逻辑类似,而且随时可以相互替换,恰符合策略模式的应用场景。

# 模式实现

# 解决方案

使用策略模式来解决问题。

  1. 首先创建抽象的算法类CashSupur,作为所有促销活动算法的抽象类,同时定义所有支持算法的公共接口,定义方法acceptCash()用于得到结果;
  2. 创建具体的促销算法类CashNormal,CashRebate等,继承于抽象算法类CashSupur,覆写acceptCash()实现具体的促销算法;
  3. 创建上下文类CashContext,维护对算法对象的引用,使用时根据用户输入,传入一个具体的促销算法类来配置。

# 代码实现

此处我们使用Java语言来实现这一方案,C#语言实现可见原书原版,本项目的所有语言实现可见本项目Github仓库,其中包括:C++ (opens new window),Java (opens new window),python (opens new window),读者可按需参阅。

首先定义一个收费抽象类。

public abstract class CashSuper{
    public abstract double acceptCash(double money);
}
1
2
3

定义具体的促销算法类,包括正常收费类CashNormal,打折收费类CashRebate,返利收费类CashReturn。

public class CashNormal extends CashSuper {
    @Override
    public double acceptCash(double money) {
        return money;
    }
}

public class CashRebate extends CashSuper {
    private double moneyRebate;

    public CashRebate(String moneyRebate) {
        this.moneyRebate = Double.parseDouble(moneyRebate);
    }

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

    public double getMoneyRebate() {
        return moneyRebate;
    }
  
    public void setMoneyRebate(double moneyRebate) {
        this.moneyRebate = moneyRebate;
    }
}

public class CashReturn extends CashSuper {
    private double moneyCondition;
    private double moneyReturn;

    public CashReturn(String moneyCondition, String moneyReturn) {
        this.moneyCondition = Double.parseDouble(moneyCondition);
        this.moneyReturn = Double.parseDouble(moneyReturn);
    }

    @Override
    public double acceptCash(double money) {
        if(money >= this.moneyCondition) {
            return money - Math.floor(money / this.moneyCondition) * moneyReturn;
        }
        return money;
    }

    public double getMoneyCondition() {
        return moneyCondition;
    }

    public void setMoneyCondition(double moneyCondition) {
        this.moneyCondition = moneyCondition;
    }

    public double getMoneyReturn() {
        return moneyReturn;
    }

    public void setMoneyReturn(double moneyReturn) {
        this.moneyReturn = moneyReturn;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

定义上下文类CashContext,维护对算法对象的引用。

public class CashContext {

    private CashSuper cs;

    public CashContext(String type) {
        switch(type){
            case "正常收费":
                this.cs = new CashNormal();
                break;
            case "满返":
                this.cs = new CashReturn("300", "100");
                break;
            case "打折":
                this.cs = new CashRebate("0.8");
                break;
        }
    }

    public double getResult(double money){
        return cs.acceptCash(money);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

客户端如下。

public class CacheClient {
    public static void main(String[] args) {
        double num = 10;
        double price = 100;

        CashContext csuper1 = new CashContext("正常收费");
        CashContext csuper2 = new CashContext("满返");
        CashContext csuper3 = new CashContext("打折");
        System.out.println(csuper1.getResult(price * num));
        System.out.println(csuper2.getResult(price * num));
        System.out.println(csuper3.getResult(price * num));
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

运行结果如下。

1000
700
800
1
2
3

# 结构组成

策略模式由三类主要角色组成:

  1. 策略类:定义所有支持算法的公共接口,在这个例子中具体为收费抽象类;
  2. 具体策略类:具体的算法,在这个例子中具体为各类收费类和折扣优惠收费类;
  3. 上下文类:维护对策略对象的应用。

结构示意图如下

案例UML

策略模式通用结构示意图如下

策略模式UML

# 模式评价

# 适用场景

一个系统中有多个算法和类很相似,区分这些类和算法的只是其内部行为。

# 实际应用

  • 在导航应用中,有不同的路径规划算法,如针对步行的、骑行的、搭乘公共交通工具的、以及开车的等。主要导航类的主要工作是在地图上渲染出规划好的路径,并不会在意是由和算法生成的路径。此时可以将路径规划算法使用策略模式进行封装,方便与主要导航类的交互。

# 优点缺点

策略模式的优点有

  • 可以以相同的方式调用所有算法,减少了各种算法类与使用算法类之间的耦合。策略模式的Strategy类层次为Context类定义了一系列的可供重复使用的算法或行为,继承有助于析取这些算法中的公共功能。
  • 简化了单元测试。每个算法都有自己的类,可以通过自己的接口单独测试;
  • 符合“开放封闭原则”,无需对上下文进行修改就可以引入新的策略。

策略模式的缺点有

  • 不适合算法极少发生改变的场景,会使得程序整体过于复杂;
  • 要求客户端必须知晓策略间的不同,因为需要从中选择;

# 参考资料

  1. 《深入设计模式》
上次更新: 2025-02-06 00:56:14

← template_method interpreter→

Copyright © 2020-2025 wuzhiyong
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式