原文地址:
https://www.cnblogs.com/lewis0077/p/5133812.html
在讲策略模式之前,我们先看一个日常生活中的小例子:
现实生活中我们到商场买东西的时候,卖场往往根据不同的客户制定不同的报价策略,比如针对新客户不打折扣,针对老客户打9折,针对VIP客户打8折...
现在我们要做一个报价管理的模块,简要点就是要针对不同的客户,提供不同的折扣报价。
如果是有你来做,你会怎么做?
我们很有可能写出下面的代码:
package strategy.examp02;
import java.math.BigDecimal;
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;
}
}
经过测试,上面的代码工作的很好,可是上面的代码是有问题的。上面存在的问题:把不同客户的报价的算法都放在了同一个方法里面,使得该方法很是庞大(现在是只是一个演示,所以看起来还不是很臃肿)。
下面看一下上面的改进,我们把不同客户的报价的算法都单独作为一个方法
package strategy.examp02;
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).
开闭原则:
对于扩展是开放的(Open for extension)。这意味着模块的行为是可以扩展的。当应用的需求改变时,我们可以对模块进行扩展,使其具有满足那些改变的新行为。也就是说,我们可以改变模块的功能。
对于修改是关闭的(Closed for modification)。对模块行为进行扩展时,不必改动模块的源代码或者二进制代码。
2.我们经常会面临这样的情况,不同的时期使用不同的报价规则,比如在各个节假日举行的各种促销活动时、商场店庆时往往都有普遍的折扣,但是促销时间一旦过去,报价就要回到正常价格上来。按照上面的代码我们就得修改if else里面的代 码很是麻烦
那有没有什么办法使得我们的报价管理即可扩展、可维护,又可以方便的响应变化呢?当然有解决方案啦,就是我们下面要讲的策略模式。
定义:
策略模式定义了一系列的算法,并将每一个算法封装起来,使每个算法可以相互替代,使算法本身和使用算法的客户端分割开来,相互独立。
结构:
1.策略接口角色IStrategy:用来约束一系列具体的策略算法,策略上下文角色ConcreteStrategy使用此策略接口来调用具体的策略所实现的算法。
2.具体策略实现角色ConcreteStrategy:具体的策略实现,即具体的算法实现。
3.策略上下文角色StrategyContext:策略上下文,负责和具体的策略实现交互,通常策略上下文对象会持有一个真正的策略实现对象,策略上下文还可以让具体的策略实现从其中获取相关数据,回调策略上下文对象的方法。
UML类图:
UML序列图:
策略模式代码的一般通用实现:
策略接口
package strategy.examp01;
//策略接口
public interface IStrategy {
//定义的抽象算法方法 来约束具体的算法实现方法
public void algorithmMethod();
}
具体的策略实现:
package strategy.examp01;
// 具体的策略实现
public class ConcreteStrategy implements IStrategy {
//具体的算法实现
@Override
public void algorithmMethod() {
System.out.println("this is ConcreteStrategy method...");
}
}
package strategy.examp01;
// 具体的策略实现2
public class ConcreteStrategy2 implements IStrategy {
//具体的算法实现
@Override
public void algorithmMethod() {
System.out.println("this is ConcreteStrategy2 method...");
}
}
策略上下文:
package strategy.examp01;
/**
* 策略上下文
*/
public class StrategyContext {
//持有一个策略实现的引用
private IStrategy strategy;
//使用构造器注入具体的策略类
public StrategyContext(IStrategy strategy) {
this.strategy = strategy;
}
public void contextMethod(){
//调用策略实现的方法
strategy.algorithmMethod();
}
}
外部客户端:
package strategy.examp01;
//外部客户端
public class Client {
public static void main(String[] args) {
//1.创建具体测策略实现
IStrategy strategy = new ConcreteStrategy2();
//2.在创建策略上下文的同时,将具体的策略实现对象注入到策略上下文当中
StrategyContext ctx = new StrategyContext(strategy);
//3.调用上下文对象的方法来完成对具体策略实现的回调
ctx.contextMethod();
}
}
针对我们一开始讲的报价管理的例子:我们可以应用策略模式对其进行改造,不同类型的客户有不同的折扣,我们可以将不同类型的客户的报价规则都封装为一个独立的算法,然后抽象出这些报价算法的公共接口
公共报价策略接口:
针对我们一开始讲的报价管理的例子:我们可以应用策略模式对其进行改造,不同类型的客户有不同的折扣,我们可以将不同类型的客户的报价规则都封装为一个独立的算法,然后抽象出这些报价算法的公共接口
公共报价策略接口:
1 package strategy.examp02;
2
3 import java.math.BigDecimal;
4 //报价策略接口
5 public interface IQuoteStrategy {
6 //获取折后价的价格
7 BigDecimal getPrice(BigDecimal originalPrice);
8 }
新客户报价策略实现:
1 package strategy.examp02;
2
3 import java.math.BigDecimal;
4 //新客户的报价策略实现类
5 public class NewCustomerQuoteStrategy implements IQuoteStrategy {
6 @Override
7 public BigDecimal getPrice(BigDecimal originalPrice) {
8 System.out.println("抱歉!新客户没有折扣!");
9 return originalPrice;
10 }
11 }
老客户报价策略实现:
1 package strategy.examp02;
2
3 import java.math.BigDecimal;
4 //老客户的报价策略实现
5 public class OldCustomerQuoteStrategy implements IQuoteStrategy {
6 @Override
7 public BigDecimal getPrice(BigDecimal originalPrice) {
8 System.out.println("恭喜!老客户享有9折优惠!");
9 originalPrice = originalPrice.multiply(new BigDecimal(0.9)).setScale(2,BigDecimal.ROUND_HALF_UP);
10 return originalPrice;
11 }
12 }
VIP客户报价策略实现:
package strategy.examp02;
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;
}
}
报价上下文:
1 package strategy.examp02;
2
3 import java.math.BigDecimal;
4 //报价上下文角色
5 public class QuoteContext {
6 //持有一个具体的报价策略
7 private IQuoteStrategy quoteStrategy;
8
9 //注入报价策略
10 public QuoteContext(IQuoteStrategy quoteStrategy){
11 this.quoteStrategy = quoteStrategy;
12 }
13
14 //回调具体报价策略的方法
15 public BigDecimal getPrice(BigDecimal originalPrice){
16 return quoteStrategy.getPrice(originalPrice);
17 }
18 }
外部客户端:
1 package strategy.examp02;
2
3 import java.math.BigDecimal;
4 //外部客户端
5 public class Client {
6 public static void main(String[] args) {
7 //1.创建老客户的报价策略
8 IQuoteStrategy oldQuoteStrategy = new OldCustomerQuoteStrategy();
9
10 //2.创建报价上下文对象,并设置具体的报价策略
11 QuoteContext quoteContext = new QuoteContext(oldQuoteStrategy);
12
13 //3.调用报价上下文的方法
14 BigDecimal price = quoteContext.getPrice(new BigDecimal(100));
15
16 System.out.println("折扣价为:" +price);
17 }
18 }
控制台输出:
恭喜!老客户享有9折优惠!
折扣价为:90.00
这个时候,商场营销部新推出了一个客户类型--MVP用户(Most Valuable Person),可以享受折扣7折优惠,那该怎么办呢?
这个很容易,只要新增一个报价策略的实现,然后外部客户端调用的时候,创建这个新增的报价策略实现,并设置到策略上下文就可以了,对原来已经实现的代码没有任何的改动。
MVP用户的报价策略实现:
1 package strategy.examp02;
2
3 import java.math.BigDecimal;
4 //MVP客户的报价策略实现
5 public class MVPCustomerQuoteStrategy implements IQuoteStrategy {
6 @Override
7 public BigDecimal getPrice(BigDecimal originalPrice) {
8 System.out.println("哇偶!MVP客户享受7折优惠!!!");
9 originalPrice = originalPrice.multiply(new BigDecimal(0.7)).setScale(2,BigDecimal.ROUND_HALF_UP);
10 return originalPrice;
11 }
12 }
外部客户端:
1 package strategy.examp02;
2
3 import java.math.BigDecimal;
4 //外部客户端
5 public class Client {
6 public static void main(String[] args) {
7 //创建MVP客户的报价策略
8 IQuoteStrategy mvpQuoteStrategy = new MVPCustomerQuoteStrategy();
9
10 //创建报价上下文对象,并设置具体的报价策略
11 QuoteContext quoteContext = new QuoteContext(mvpQuoteStrategy);
12
13 //调用报价上下文的方法
14 BigDecimal price = quoteContext.getPrice(new BigDecimal(100));
15
16 System.out.println("折扣价为:" +price);
17 }
18 }
控制台输出:
哇偶!MVP客户享受7折优惠!!!
折扣价为:70.00
深入理解策略模式:
策略模式的作用:就是把具体的算法实现从业务逻辑中剥离出来,成为一系列独立算法类,使得它们可以相互替换。
策略模式的着重点:不是如何来实现算法,而是如何组织和调用这些算法,从而让我们的程序结构更加的灵活、可扩展。
我们前面的第一个报价管理的示例,发现每个策略算法实现对应的都是在QuoteManager 中quote方法中的if else语句里面,我们知道if else if语句里面的代码在执行的可能性方面可以说是平等的,你要么执行if,要么执行else,要么执行else if。
策略模式就是把各个平等的具体实现进行抽象、封装成为独立的算法类,然后通过上下文和具体的算法类来进行交互。各个策略算法都是平等的,地位是一样的,正是由于各个算法的平等性,所以它们才是可以相互替换的。虽然我们可以动态的切换各个策略,但是同一时刻只能使用一个策略。