笔记 笔记
首页
  • 开发工具
  • Java Web
  • Java 进阶
  • 容器化技术
  • Java 专栏

    • Java 核心技术面试精讲
    • Java 业务开发常见错误 100 例
  • 数据库专栏

    • MySQL 实战 45 讲
    • Redis 核心技术与实战
  • 安全专栏

    • OAuth 2.0 实战课
  • 计算机系统
  • 程序设计语言
  • 数据结构
  • 知识产权
  • 数据库
  • 面向对象
  • UML
  • 设计模式
  • 操作系统
  • 结构化开发
  • 软件工程
  • 计算机网络
  • 上午题错题
在线工具 (opens new window)

EasT-Duan

Java 开发
首页
  • 开发工具
  • Java Web
  • Java 进阶
  • 容器化技术
  • Java 专栏

    • Java 核心技术面试精讲
    • Java 业务开发常见错误 100 例
  • 数据库专栏

    • MySQL 实战 45 讲
    • Redis 核心技术与实战
  • 安全专栏

    • OAuth 2.0 实战课
  • 计算机系统
  • 程序设计语言
  • 数据结构
  • 知识产权
  • 数据库
  • 面向对象
  • UML
  • 设计模式
  • 操作系统
  • 结构化开发
  • 软件工程
  • 计算机网络
  • 上午题错题
在线工具 (opens new window)

购买兑换码请添加

添加时候请写好备注,否则无法通过。

  • 设计模式

    • 简介

    • 原则

    • UML

    • 创建型

    • 结构型

    • 行为型

      • 模板模式
      • 命令模式
      • 访问者模式
      • 迭代器模式
      • 中介者模式
      • 备忘录模式
      • 解释器模式
      • 状态模式
      • 策略模式
        • 简介
        • 代码示例
          • 策略模式
        • 说明
        • 拓展
          • 电商下单问题
        • JDK 源码中对策略模式的应用
      • 职责链模式
  • JVM 详解

  • Linux

  • Redis

  • 分布式锁

  • Shiro

  • Gradle

  • Java 进阶
  • 设计模式
  • 行为型
EasT-Duan
2023-12-05
目录

策略模式

欢迎来到我的 ChatGPT 中转站,极具性价比,为付费不方便的朋友提供便利,有需求的可以添加左侧 QQ 二维码,另外,邀请新用户能获取余额哦!最后说一句,那啥:请自觉遵守《生成式人工智能服务管理暂行办法》。

# 简介

策略模式(Strategy Pattern)中,定义算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。

这算法体现了几个设计原则:

  1. 把变化的代码从不变的代码中分离出来。
  2. 针对接口编程而不是具体类(定义了策略接口)。
  3. 多用组合 / 聚合,少用继承(客户通过组合方式使用策略)。

从图中可以看到,Context 拥有一个 Strategy 对象,至于需要使用到哪个策略,可以从在构造中进行定义。

# 代码示例

需求:编写鸭子项目,具体要求如下:

  • 有各种鸭子(比如野鸭、北京鸭、玩具鸭等,鸭子有各种行为,比如叫、飞行等)。
  • 显示鸭子的信息。

分析:如果按照传统模式,所有种类的鸭子都继承抽象的类 Duck。

点击查看
  1. 其他鸭子都继承了 Duck 类,所以 fly 让所有子类都会飞了,这是不正确的。
  2. 上面说的问题,其实是继承带来的问题:对类的局部改动,尤其超类的局部改动,会影响其他部分,会有溢出效应。
  3. 为了改进 1 问题 1,我们可以通过覆盖 fly 方法来解决,如上图所示。
    4. 问题又来了,如果我们有一个玩具鸭子 ToyDuck,这样就需要 ToyDuck 去覆盖 Duck 的所有实现的方法。

# 策略模式

分别封装鸭子行为接口,实现算法族,超类里放行为接口对象,在子类里具体设定行为对象。原则就是:分离变化部分,封装接口,基于接口编程各种功能。

/**
 * 飞翔的行为
 */
public interface FlyBehavior {
    void fly(String name); // 子类具体实现
}
1
2
3
4
5
6
public class GoodFlyBehavior implements FlyBehavior {
    @Override
    public void fly(String name) {
        System.out.println(name + "飞翔技术高超");
    }

}
1
2
3
4
5
6
7
public class BadFlyBehavior implements FlyBehavior {

    @Override
    public void fly(String name) {
        System.out.println(name + "飞翔技术一般");
    }
}
1
2
3
4
5
6
7
public class NoFlyBehavior implements FlyBehavior {

    @Override
    public void fly(String name) {
        System.out.println(name + "不会飞翔");
    }

}
1
2
3
4
5
6
7
8
package strategy.improve;

/**
 * 叫的行为
 */
public interface QuackBehavior {
    void quack(String name);//子类实现
}
1
2
3
4
5
6
7
8
/**
 * 不会叫
 */
public class NoQuack implements QuackBehavior {
    @Override
    public void quack(String name) {
        System.out.println(name+"不会叫");
    }
}
1
2
3
4
5
6
7
8
9
/**
 * 大声的叫
 */
public class LoudQuack implements QuackBehavior {
    @Override
    public void quack(String name) {
        System.out.println(name + "大声喊叫");
    }
}
1
2
3
4
5
6
7
8
9
@Setter
public abstract class Duck {
    private String name;

    public Duck(String name) {
        this.name = name;
    }

    //属性, 策略接口
    protected FlyBehavior flyBehavior;
    //其它属性<->策略接口
    protected QuackBehavior quackBehavior;

    public void quack() {
        if (quackBehavior != null) {
            quackBehavior.quack(name);
        }
    }

    public void fly() {
        if (flyBehavior != null) {
            flyBehavior.fly(name);
        }
    }

    public void swim() {
        System.out.println("鸭子会游泳~~");
    }
}
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
/**
 * 玩具鸭
 */
public class ToyDuck extends Duck {
    public ToyDuck() {
        super("玩具鸭");
        flyBehavior = new NoFlyBehavior();
        quackBehavior = new NoQuack();
    }

    @Override
    public void quack() {
        super.quack();
    }

    @Override
    public void swim() {
        super.swim();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class WildDuck extends Duck {
//构造器,传入FlyBehavor 的对象
    public WildDuck() {
        super("野鸭");
        flyBehavior = new GoodFlyBehavior();
        quackBehavior = new LoudQuack();
    }
}
1
2
3
4
5
6
7
8
public class PekingDuck extends Duck {
	//假如北京鸭可以飞翔,但是飞翔技术一般
	public PekingDuck() {
		super("北京鸭");
		flyBehavior = new BadFlyBehavior();
	}
}
1
2
3
4
5
6
7
public class Client {

    public static void main(String[] args) {
        WildDuck wildDuck = new WildDuck();
        wildDuck.fly();//
        wildDuck.quack();


        ToyDuck toyDuck = new ToyDuck();
        toyDuck.fly();
        toyDuck.quack();

        PekingDuck pekingDuck = new PekingDuck();
        pekingDuck.fly();

        //动态改变某个对象的行为, 北京鸭 不能飞
        pekingDuck.setFlyBehavior(new NoFlyBehavior());
        System.out.println("北京鸭的实际飞翔能力");
        pekingDuck.fly();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 说明

策略模式的关键是:分析项目中变化部分与不变部分。

策略模式的核心思想是:多用组合 / 聚合少用继承;用行为类组合,而不是行为的继承。更有弹性。

体现了 “对修改关闭,对扩展开放” 原则,客户端增加行为不用修改原有代码,只要添加一种策略即可,避免了使用多重转移语句(if..else if..else)。

提供了可以替换继承关系的办法:策略模式将算法封装在独立的 Strategy 类中使得你可以独立于其 Context 改变它,使它易于切换、易于理解、易于扩展。

需要注意的是:每添加一个策略就要增加一个类,当策略过多是会导致类数目庞大。

# 拓展

# 电商下单问题

模拟在购买商品时候使用的各种类型优惠券 (满减、直减、折扣、n 元购)。这是一个对与策略模式最直观的理解。

如果使用传统方式解决,最开始可能是一个最简单的优惠券,后面随着产品功能的增加,不断的扩展 if 语句。实际的代码可能要比这个多很多。

/**
 * 优惠券折扣计算接口
 * <p>
 * 优惠券类型;
 * 1. 直减券
 * 2. 满减券
 * 3. 折扣券
 * 4. n元购
 */
public class CouponDiscountService {

    public double discountAmount(int type, double typeContent, double skuPrice, double typeExt) {
        // 1. 直减券
        if (1 == type) {
            return skuPrice - typeContent;
        }
        // 2. 满减券
        if (2 == type) {
            if (skuPrice < typeExt) return skuPrice;
            return skuPrice - typeContent;
        }
        // 3. 折扣券
        if (3 == type) {
            return skuPrice * typeContent;
        }
        // 4. n元购
        if (4 == type) {
            return typeContent;
        }
        return 0D;
    }

}
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

用策略模式解决这一问题,先画出 UML 类图。

/**
 * 折扣策略
 */
public interface ICouponDiscount<T> {
    /**
     * 优惠券金额计算
     * @param couponInfo 券折扣信息;直减、满减、折扣、N元购
     * @param skuPrice   sku金额
     * @return 优惠后金额
     */
    BigDecimal discountAmount(T couponInfo, BigDecimal skuPrice);
}
1
2
3
4
5
6
7
8
9
10
11
12
/**
 * 满减优惠
 */
public class MJCouponDiscount implements ICouponDiscount<Map<String, BigDecimal>> {
    /**
     * 满减计算
     * 1. 判断满足x元后-n元,否则不减
     * 2. 最低支付金额1元
     */
    @Override
    public BigDecimal discountAmount(Map<String, BigDecimal> couponInfo, BigDecimal skuPrice) {
        BigDecimal x = couponInfo.get("50");
        // 商品金额小于满减条件的,直接返回商品原价
        if (skuPrice.compareTo(x) < 0) return skuPrice;
        // 减去优惠金额判断
        return skuPrice.subtract(x).compareTo(BigDecimal.ZERO) < 1 ? BigDecimal.valueOf(0.1f) : skuPrice.subtract(x);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
 * N元购
 */
public class NYGCouponDiscount implements ICouponDiscount<Double> {
    /**
     *  n元购购买
     *  无论原价多少钱都固定金额购买
     */
    @Override
    public BigDecimal discountAmount(Double couponInfo, BigDecimal skuPrice) {
        return new BigDecimal(couponInfo);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
 * 直减
 */
public class ZJCouponDiscount implements ICouponDiscount<Double> {
    /**
     * 直减计算
     * 1. 使用商品价格减去优惠价格
     * 2. 最低支付金额0.1元
     */
    @Override
    public BigDecimal discountAmount(Double couponInfo, BigDecimal skuPrice) {
        BigDecimal discountAmount = skuPrice.subtract(new BigDecimal(couponInfo));
        if (discountAmount.compareTo(BigDecimal.ZERO) < 1) return BigDecimal.valueOf(0.1f);
        return discountAmount;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
 * 折扣优惠
 */
public class ZKCouponDiscount implements ICouponDiscount<Double> {
    /**
     * 折扣计算
     * 1. 使用商品价格乘以折扣比例,为最后支付金额
     * 2. 保留两位小数
     * 3. 最低支付金额0.1元
     */
    @Override
    public BigDecimal discountAmount(Double couponInfo, BigDecimal skuPrice) {
        //计算折扣金额,使用了商品价格乘以折扣信息,然后通过 setScale 方法设置小数点后两位,并使用 BigDecimal.ROUND_HALF_UP 进行四舍五入。
        BigDecimal discountAmount = skuPrice.multiply(new BigDecimal(couponInfo)).setScale(2, BigDecimal.ROUND_HALF_UP);
        if (discountAmount.compareTo(BigDecimal.ZERO) < 1) return BigDecimal.ONE;
        return discountAmount;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
 * 策略控制
 */
@AllArgsConstructor
public class Context<T> {
    private ICouponDiscount<T> couponDiscount;

    public BigDecimal discountAmount(T couponInfo, BigDecimal skuPrice) {
        return couponDiscount.discountAmount(couponInfo, skuPrice);
    }
}
1
2
3
4
5
6
7
8
9
10
11
/**
 * 用户
 */
public class Client {
    public static void main(String[] args) {
        // 直减
        ZJCouponDiscount zjCouponDiscount = new ZJCouponDiscount();
        Context<Double> zj = new Context<>(zjCouponDiscount);
        BigDecimal discountAmount = zj.discountAmount(20d, new BigDecimal(100));
        System.out.println("直减优惠后金额 :" + discountAmount);
        // 活动还有满 50 - 5
        MJCouponDiscount mjCouponDiscount = new MJCouponDiscount();
        Context<Map<String, BigDecimal>> mj = new Context<>(mjCouponDiscount);
        Map<String, BigDecimal> coupon = new HashMap<>();
        coupon.put("50", new BigDecimal(5));
        discountAmount = mj.discountAmount(coupon, discountAmount);
        System.out.println("满减优惠后金额 :" + discountAmount);
        // 用户还有75%折扣
        ZKCouponDiscount zkCouponDiscount = new ZKCouponDiscount();
        Context<Double> zk = new Context<>(zkCouponDiscount);
        discountAmount = zk.discountAmount(0.75d, discountAmount);
        System.out.println("折扣优惠后金额 :" + discountAmount);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# JDK 源码中对策略模式的应用

在 JDK 源码 Arrays.sort (T [] a,Comparator c) 中,可以传入一个自定义的 Comparator 对象作为参数,这个 Comparator 对象就是一个策略,它决定了数组应该如何被排序。这就是策略模式的应用。

以下是一个例子:

// 定义一个策略,按照字符串的长度进行排序
// 其实这个 Comparator 就是策略模式中 strategy 类,我们写了一个匿名内部类,其实是对它的实现。
Comparator<String> comparator = new Comparator<String>() {
    /**
    * 如果返回-1,表示第一个对象小于第二个对象,即在排序中应排在第二个对象之前。
    * 如果返回0,表示两个对象相等,即它们的排序位置是相同的,或者说它们的排序位置是不确定的。
    * 如果返回1,表示第一个对象大于第二个对象,即在排序中应排在第二个对象之后。
    */
    @Override
    public int compare(String s1, String s2) {
        // 这里就是我们自定义的策略,
        return s1.length() - s2.length();
    }
};

String[] strings = {"abc", "a", "ab"};
Arrays.sort(strings, comparator);

// 输出:[a, ab, abc]
System.out.println(Arrays.toString(strings));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#设计模式
上次更新: 2025/04/12, 05:37:39
状态模式
职责链模式

← 状态模式 职责链模式→

最近更新
01
Reactor 核心
02-24
02
前置条件
10-30
03
计算机网络
09-13
更多文章>
Theme by Vdoing | Copyright © 2019-2025 powered by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式