策略模式
本篇策略模式,借鉴参考《大话设计模式》中的例子,使用了 Java 来进行简单的实现。
一个商场收银系统,营业员根据客户所购买商品的单价和数量,可能需要根据不同的促销活动计算不同的折扣,向客户收费。
mermaid的类图在手机上显示不友好,推荐使用电脑进行阅读。也可以克隆项目到本地,在 idea 中安装mermaid插件进行阅读。项目地址:design-patterns ,将会持续更新各种模式的应用,以及后续的实际案例应用。
普通实现
public class Cashier {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入商品单价:");
String unitPrice = scanner.nextLine();
System.out.print("请输入商品数量:");
String quantity = scanner.nextLine();
System.out.println("1. 9 折");
System.out.println("2. 8 折");
System.out.println("3. 7 折");
System.out.println("4. 不打折");
System.out.println("请选择折扣:");
String discount = scanner.nextLine();
double result;
switch (discount) {
case "1":
result = Double.parseDouble(unitPrice) * Double.parseDouble(quantity) * 0.9;
break;
case "2":
result = Double.parseDouble(unitPrice) * Double.parseDouble(quantity) * 0.8;
break;
case "3":
result = Double.parseDouble(unitPrice) * Double.parseDouble(quantity) * 0.7;
break;
case "4":
result = Double.parseDouble(unitPrice) * Double.parseDouble(quantity);
break;
default:
System.out.println("折扣选择错误");
return;
}
System.out.printf("您购买了 %s 件商品,单价 %s,折扣方案:%s,总价格为:%s %n", quantity, unitPrice, discount, result);
}
}
以上代码是一个简单的商场收银系统的实现,在控制台输入商品单价和购买的商品数量,并选择对应的折扣,就可以计算出商品的最终价格。
可以观察一下代码,switch 的各个分支语句其实只是执行了打几折的算法动作,除了折扣不同以外,并没有什么区别;如果现在又来个需求,商场推出了满减活动,满
300 减 20,这个怎么实现呢?继续添加条件判断已经不合适了,因为活动力度可能会随时变化,或者再推出送积分活动,满 100 送 10
积分,可以使用积分兑换商品等。
这里可能想到用简单工厂模式去实现,但是仔细思考一下,每次推出一个活动,九折、八折、七折、六折、满 300 减 20、满 100 减 5 ...... 都需要新增一个子类,这样就不太适合使用简单工厂实现了,如果对象类型较少,并且不太可能增加新的类型,那么使用简单工厂模式更加简单和方便。
改造策略模式
当需要运行根据不同的条件,选择不同的算法或行为时,可以使用策略模式。策略模式将算法或行为封装成独立的类,并且使它们可以互相替换。这样可以使算法或行为的变化独立于使用它们的客户端代码。
策略模式的核心是定义一个策略接口,该接口定义了算法或行为的共同方法。然后,在具体算法或行为的类中实现该接口,以便在运行时选择不同的算法或行为。
在此商场收银系统的案例中,可以总结一下相同点,打九折、打八折、打七折...... 等均是同一个行为或者说同一类算法,只不过打折的力度不同。
以下是策略模式的一般实现步骤:
- 定义一个策略接口(或抽象类),它定义了算法或行为的方法:
public interface Strategy {
public void doSomething();
}
- 实现策略接口的具体策略类,每个具体策略类实现了策略接口中的方法。
public class ConcreteStrategyA implements Strategy {
public void doSomething() {
// 实现具体策略A的方法
}
}
public class ConcreteStrategyB implements Strategy {
public void doSomething() {
// 实现具体策略B的方法
}
}
- 定义一个上下文类,它持有策略接口的引用,可以在运行时动态地切换不同的策略。
public class Context {
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
public void executeStrategy() {
strategy.doSomething();
}
}
- 在客户端代码中,创建具体的策略对象,并将它们注入到上下文类中,然后调用上下文类的方法来执行具体的策略。
public class Client {
public static void main(String[] args) {
Strategy strategyA = new ConcreteStrategyA();
Strategy strategyB = new ConcreteStrategyB();
Context context = new Context(strategyA);
context.executeStrategy(); // 执行具体策略A的方法
context.setStrategy(strategyB);
context.executeStrategy(); // 执行具体策略B的方法
}
}
收银系统策略模式实现
参考一般策略模式的步骤
- 定义一个计算支付金额的抽象类,并定义一个抽象方法:
public abstract class CashSuper {
/**
* 计算需支付金额
*
* @param money 钱
* @return {@link Double} 折后价格
*/
public abstract Double acceptCash(Double money);
}
- 实现具体的策略类(也就是各种折扣类)
/**
* 正常收费算法
*/
public class CashNormal extends CashSuper {
@Override
public Double acceptCash(Double money) {
return money;
}
}
/**
* 折扣算法
*/
public class CashRebate extends CashSuper {
private final Double moneyRebate;
/**
* 折扣算法
*
* @param moneyRebate 折扣
*/
public CashRebate(Double moneyRebate) {
this.moneyRebate = moneyRebate;
}
@Override
public Double acceptCash(Double money) {
return money * moneyRebate;
}
}
/**
* 满减算法
*/
public class CashReturn extends CashSuper {
private final Double moneyCondition;
private final Double moneyReturn;
/**
* 满减算法
*
* @param moneyCondition 满减条件
* @param moneyReturn 满减金额
*/
public CashReturn(Double moneyCondition, Double moneyReturn) {
this.moneyCondition = moneyCondition;
this.moneyReturn = moneyReturn;
}
@Override
public Double acceptCash(Double money) {
if (money >= moneyCondition) {
return money - (money / moneyCondition) * moneyReturn;
}
return money;
}
}
- 定义一个上下文类
public class CashContext {
private CashSuper cashSuper;
public CashContext(String type) {
switch (type) {
case "1":
cashSuper = new CashRebate(0.9d);
break;
case "2":
cashSuper = new CashRebate(0.8d);
break;
case "3":
cashSuper = new CashRebate(0.7d);
break;
case "4":
cashSuper = new CashNormal();
break;
case "5":
cashSuper = new CashReturn(300d, 100d);
break;
default:
System.out.println("折扣选择错误");
}
}
public Double getResult(Double quantity, Double unitPrice) {
return cashSuper.acceptCash(quantity * unitPrice);
}
}
此处简单工厂模式和策略模式一起使用,将简单工厂模式用来创建不同的策略对象,然后将这些策略对象注入到策略模式的上下文中,这样就可以动态的选择不同的算法或行为。
- 客户端
public class Cashier {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入商品单价:");
String unitPrice = scanner.nextLine();
System.out.print("请输入商品数量:");
String quantity = scanner.nextLine();
System.out.println("1. 9 折");
System.out.println("2. 8 折");
System.out.println("3. 7 折");
System.out.println("4. 不打折");
System.out.println("5. 满 300 减 100");
System.out.println("请选择折扣:");
String discount = scanner.nextLine();
CashContext cc = new CashContext(discount);
double result = cc.getResult(Double.parseDouble(quantity), Double.parseDouble(unitPrice));
System.out.printf("您购买了 %s 件商品,单价 %s,折扣方案:%s,总价格为:%s %n", quantity, unitPrice, discount, result);
}
}
结构图
---
title: 商场收银系统结构图
---
classDiagram
direction rl
CashNormal --|> CashSuper
CashRebate --|> CashSuper
CashReturn --|> CashSuper
CashContext o--> CashSuper
class CashSuper {
<<Abstract>>
+ acceptCash(Double money) Double
}
class CashContext {
- cashSuper CashSuper
+ CashContext(String type)
+ getResult(Double quantity, Double unitPrice) Double
}
class CashNormal {
+ acceptCash(Double money) Double
}
class CashRebate {
+ CashRebate(Double moneyRebate)
+ acceptCash(Double money) Double
}
class CashReturn {
- moneyCondition Double
- moneyReturn Double
+ CashReturn(Double moneyCondition, Double moneyReturn)
+ acceptCash(Double money) Double
}
优缺点
优点
-
策略模式能够封装算法,使得算法可以独立于客户端而变化,增加了程序的灵活性。
-
策略模式提供了一种解耦的方式,将算法与客户端代码分离开来,使得代码更加易于维护和扩展。
-
策略模式可以避免大量的条件判断语句,提高代码的可读性和可维护性。
缺点
-
策略模式增加了代码的复杂度,需要在客户端代码中指定使用哪种算法。
-
如果算法很多,就需要创建很多的策略类,增加了类的数量,可能会影响程序的性能。
适用场景
-
多种算法实现:如果一个问题有多种解决方案或者多种算法实现,可以使用策略模式将这些算法实现封装起来,使得客户端代码可以动态地选择不同的算法实现。
-
避免使用大量的条件语句:如果一个问题需要根据不同的条件来选择不同的算法实现,使用大量的条件语句会使得代码难以维护和扩展。使用策略模式可以将不同的算法实现封装为不同的策略类,避免使用大量的条件语句。
-
代码解耦:使用策略模式可以将算法实现与客户端代码解耦,使得客户端代码更加灵活,可以轻松地切换不同的算法实现。
-
可扩展性:策略模式可以轻松地添加新的算法实现,使得系统更加灵活和可扩展。
-
单一职责原则:策略模式可以使得每个策略类只负责一个算法实现,符合单一职责原则,使得代码更加清晰和易于维护。