从业务出发,来谈谈策略模式,清爽的飞起~
共 9259字,需浏览 19分钟
·
2020-08-17 21:41
点击上方蓝色“程序猿DD”,选择“设为星标”
回复“资源”获取独家整理的学习资料!
作者 | 路易小七
在讲策略模式之前,我们先看一个日常生活中的小例子:
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;
}
}
对于扩展是开放的(Open for extension)。这意味着模块的行为是可以扩展的。当应用的需求改变时,我们可以对模块进行扩展,使其具有满足那些改变的新行为。也就是说,我们可以改变模块的功能。
对于修改是关闭的(Closed for modification)。对模块行为进行扩展时,不必改动模块的源代码或者二进制代码。
定义:
结构:
策略接口角色IStrategy:用来约束一系列具体的策略算法,策略上下文角色ConcreteStrategy使用此策略接口来调用具体的策略所实现的算法。
具体策略实现角色ConcreteStrategy:具体的策略实现,即具体的算法实现。
策略上下文角色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();
}
}
package strategy.examp02;
import java.math.BigDecimal;
//报价策略接口
public interface IQuoteStrategy {
//获取折后价的价格
BigDecimal getPrice(BigDecimal originalPrice);
}
package strategy.examp02;
import java.math.BigDecimal;
//新客户的报价策略实现类
public class NewCustomerQuoteStrategy implements IQuoteStrategy {
@Override
public BigDecimal getPrice(BigDecimal originalPrice) {
System.out.println("抱歉!新客户没有折扣!");
return originalPrice;
}
}
package strategy.examp02;
import java.math.BigDecimal;
//老客户的报价策略实现
public class OldCustomerQuoteStrategy implements IQuoteStrategy {
@Override
public BigDecimal getPrice(BigDecimal originalPrice) {
System.out.println("恭喜!老客户享有9折优惠!");
originalPrice = originalPrice.multiply(new BigDecimal(0.9)).setScale(2,BigDecimal.ROUND_HALF_UP);
return originalPrice;
}
}
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;
}
}
package strategy.examp02;
import java.math.BigDecimal;
//报价上下文角色
public class QuoteContext {
//持有一个具体的报价策略
private IQuoteStrategy quoteStrategy;
//注入报价策略
public QuoteContext(IQuoteStrategy quoteStrategy){
this.quoteStrategy = quoteStrategy;
}
//回调具体报价策略的方法
public BigDecimal getPrice(BigDecimal originalPrice){
return quoteStrategy.getPrice(originalPrice);
}
}
package strategy.examp02;
import java.math.BigDecimal;
//外部客户端
public class Client {
public static void main(String[] args) {
//1.创建老客户的报价策略
IQuoteStrategy oldQuoteStrategy = new OldCustomerQuoteStrategy();
//2.创建报价上下文对象,并设置具体的报价策略
QuoteContext quoteContext = new QuoteContext(oldQuoteStrategy);
//3.调用报价上下文的方法
BigDecimal price = quoteContext.getPrice(new BigDecimal(100));
System.out.println("折扣价为:" +price);
}
}
恭喜!老客户享有9折优惠!
折扣价为:90.00
package strategy.examp02;
import java.math.BigDecimal;
//MVP客户的报价策略实现
public class MVPCustomerQuoteStrategy implements IQuoteStrategy {
@Override
public BigDecimal getPrice(BigDecimal originalPrice) {
System.out.println("哇偶!MVP客户享受7折优惠!!!");
originalPrice = originalPrice.multiply(new BigDecimal(0.7)).setScale(2,BigDecimal.ROUND_HALF_UP);
return originalPrice;
}
}
package strategy.examp02;
import java.math.BigDecimal;
//外部客户端
public class Client {
public static void main(String[] args) {
//创建MVP客户的报价策略
IQuoteStrategy mvpQuoteStrategy = new MVPCustomerQuoteStrategy();
//创建报价上下文对象,并设置具体的报价策略
QuoteContext quoteContext = new QuoteContext(mvpQuoteStrategy);
//调用报价上下文的方法
BigDecimal price = quoteContext.getPrice(new BigDecimal(100));
System.out.println("折扣价为:" +price);
}
}
哇偶!MVP客户享受7折优惠!!!
折扣价为:70.00
深入理解策略模式:
策略模式的作用:就是把具体的算法实现从业务逻辑中剥离出来,成为一系列独立算法类,使得它们可以相互替换。
策略模式的着重点:不是如何来实现算法,而是如何组织和调用这些算法,从而让我们的程序结构更加的灵活、可扩展。
上策:挑选精兵,昼夜兼行直接偷袭成都,可以一举而定,此为上计计也。 中策:杨怀、高沛是蜀中名将,手下有精锐部队,而且据守关头,我们可以装作要回荆州,引他们轻骑来见,可就此将其擒杀,而后进兵成都,此为中计。 下策:退还白帝,连引荆州,慢慢进图益州,此为下计。
//攻取西川的策略
public interface IOccupationStrategyWestOfSiChuan {
public void occupationWestOfSiChuan(String msg);
}
//攻取西川的上上计策
public class UpperStrategy implements IOccupationStrategyWestOfSiChuan {
@Override
public void occupationWestOfSiChuan(String msg) {
if (msg == null || msg.length() < 5) {
//故意设置障碍,导致上上计策失败
System.out.println("由于计划泄露,上上计策失败!");
int i = 100/0;
}
System.out.println("挑选精兵,昼夜兼行直接偷袭成都,可以一举而定,此为上计计也!");
}
}
//攻取西川的中计策
public class MiddleStrategy implements IOccupationStrategyWestOfSiChuan {
@Override
public void occupationWestOfSiChuan(String msg) {
System.out.println("杨怀、高沛是蜀中名将,手下有精锐部队,而且据守关头,我们可以装作要回荆州,引他们轻骑来见,可就此将其擒杀,而后进兵成都,此为中计。");
}
}
//攻取西川的下计策
public class LowerStrategy implements IOccupationStrategyWestOfSiChuan {
@Override
public void occupationWestOfSiChuan(String msg) {
System.out.println("退还白帝,连引荆州,慢慢进图益州,此为下计。");
}
}
//攻取西川参谋部,就是上下文啦,由上下文来选择具体的策略
public class OccupationContext {
public void occupationWestOfSichuan(String msg){
//先用上上计策
IOccupationStrategyWestOfSiChuan strategy = new UpperStrategy();
try {
strategy.occupationWestOfSiChuan(msg);
} catch (Exception e) {
//上上计策有问题行不通之后,用中计策
strategy = new MiddleStrategy();
strategy.occupationWestOfSiChuan(msg);
}
}
}
//此时外部客户端相当于刘备了,不管具体采用什么计策,只要结果(成功的攻下西川)
public class Client {
public static void main(String[] args) {
OccupationContext context = new OccupationContext();
//这个给手下的人激励不够啊
context.occupationWestOfSichuan("拿下西川");
System.out.println("=========================");
//这个人人有赏,让士兵有动力啊
context.occupationWestOfSichuan("拿下西川之后,人人有赏!");
}
}
由于计划泄露,上上计策失败!
杨怀、高沛是蜀中名将,手下有精锐部队,而且据守关头,我们可以装作要回荆州,引他们轻骑来见,可就此将其擒杀,而后进兵成都,此为中计。
=========================
挑选精兵,昼夜兼行直接偷袭成都,可以一举而定,此为上计计也!
策略和上下文的关系:
//支付策略接口
public interface PayStrategy {
//在支付策略接口的支付方法中含有支付上下文作为参数,以便在具体的支付策略中回调上下文中的方法获取数据
public void pay(PayContext ctx);
}
//人民币支付策略
public class RMBPay implements PayStrategy {
@Override
public void pay(PayContext ctx) {
System.out.println("现在给:"+ctx.getUsername()+" 人民币支付 "+ctx.getMoney()+"元!");
}
}
//美金支付策略
public class DollarPay implements PayStrategy {
@Override
public void pay(PayContext ctx) {
System.out.println("现在给:"+ctx.getUsername()+" 美金支付 "+ctx.getMoney()+"dollar !");
}
}
//支付上下文,含有多个算法的公有数据
public class PayContext {
//员工姓名
private String username;
//员工的工资
private double money;
//支付策略
private PayStrategy payStrategy;
public void pay(){
//调用具体的支付策略来进行支付
payStrategy.pay(this);
}
public PayContext(String username, double money, PayStrategy payStrategy) {
this.username = username;
this.money = money;
this.payStrategy = payStrategy;
}
public String getUsername() {
return username;
}
public double getMoney() {
return money;
}
}
//外部客户端
public class Client {
public static void main(String[] args) {
//创建具体的支付策略
PayStrategy rmbStrategy = new RMBPay();
PayStrategy dollarStrategy = new DollarPay();
//准备小王的支付上下文
PayContext ctx = new PayContext("小王",30000,rmbStrategy);
//向小王支付工资
ctx.pay();
//准备Jack的支付上下文
ctx = new PayContext("jack",10000,dollarStrategy);
//向Jack支付工资
ctx.pay();
}
}
现在给:小王 人民币支付 30000.0元!
现在给:jack 美金支付 10000.0dollar !
//银行账户支付
public class AccountPay implements PayStrategy {
@Override
public void pay(PayContext ctx) {
PayContextWithAccount ctxAccount = (PayContextWithAccount) ctx;
System.out.println("现在给:"+ctxAccount.getUsername()+"的账户:"+ctxAccount.getAccount()+" 支付工资:"+ctxAccount.getMoney()+" 元!");
}
}
//带银行账户的支付上下文
public class PayContextWithAccount extends PayContext {
//银行账户
private String account;
public PayContextWithAccount(String username, double money, PayStrategy payStrategy,String account) {
super(username, money, payStrategy);
this.account = account;
}
public String getAccount() {
return account;
}
}
//外部客户端
public class Client {
public static void main(String[] args) {
//创建具体的支付策略
PayStrategy rmbStrategy = new RMBPay();
PayStrategy dollarStrategy = new DollarPay();
//准备小王的支付上下文
PayContext ctx = new PayContext("小王",30000,rmbStrategy);
//向小王支付工资
ctx.pay();
//准备Jack的支付上下文
ctx = new PayContext("jack",10000,dollarStrategy);
//向Jack支付工资
ctx.pay();
//创建支付到银行账户的支付策略
PayStrategy accountStrategy = new AccountPay();
//准备带有银行账户的上下文
ctx = new PayContextWithAccount("小张",40000,accountStrategy,"1234567890");
//向小张的账户支付
ctx.pay();
}
}
现在给:小王 人民币支付 30000.0元!
现在给:jack 美金支付 10000.0dollar !
现在给:小张的账户:1234567890 支付工资:40000.0 元!
//支付到银行账户的策略
public class AccountPay2 implements PayStrategy {
//银行账户
private String account;
public AccountPay2(String account) {
this.account = account;
}
@Override
public void pay(PayContext ctx) {
System.out.println("现在给:"+ctx.getUsername()+"的账户:"+getAccount()+" 支付工资:"+ctx.getMoney()+" 元!");
}
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
}
//外部客户端
public class Client {
public static void main(String[] args) {
//创建具体的支付策略
PayStrategy rmbStrategy = new RMBPay();
PayStrategy dollarStrategy = new DollarPay();
//准备小王的支付上下文
PayContext ctx = new PayContext("小王",30000,rmbStrategy);
//向小王支付工资
ctx.pay();
//准备Jack的支付上下文
ctx = new PayContext("jack",10000,dollarStrategy);
//向Jack支付工资
ctx.pay();
//创建支付到银行账户的支付策略
PayStrategy accountStrategy = new AccountPay2("1234567890");
//准备上下文
ctx = new PayContext("小张",40000,accountStrategy);
//向小张的账户支付
ctx.pay();
}
}
现在给:小王 人民币支付 30000.0元!
现在给:jack 美金支付 10000.0dollar !
现在给:小张的账户:1234567890 支付工资:40000.0 元!
优点:具体的策略实现风格很是统一,策略实现所需要的数据都是从上下文中获取的,在上下文中添加的数据,可以视为公共的数据,其他的策略实现也可以使用。
缺点:很明显如果某些数据只是特定的策略实现需要,大部分的策略实现不需要,那这些数据有“浪费”之嫌,另外如果每次添加算法数据都扩展上下文,很容易导致上下文的层级很是复杂。
优点:容易想到,实现简单
缺点:与其他的策略实现风格不一致,其他的策略实现所需数据都是来自上下文,而这个策略实现一部分数据来自于自身,一部分数据来自于上下文;外部在使用这个策略实现的时候也和其他的策略实现不一致了,难以以一个统一的方式动态的切换策略实现。
策略模式在JDK中的应用:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueueworkQueue, {
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
corePoolSize:线程池中的核心线程数量,即使这些线程没有任务干,也不会将其销毁。 maximumPoolSize:线程池中的最多能够创建的线程数量。 keepAliveTime:当线程池中的线程数量大于corePoolSize时,多余的线程等待新任务的最长时间。 unit:keepAliveTime的时间单位。 workQueue:在线程池中的线程还没有还得及执行任务之前,保存任务的队列(当线程池中的线程都有任务在执行的时候,仍然有任务不断的提交过来,这些任务保存在workQueue队列中)。 threadFactory:创建线程池中线程的工厂。 handler:当线程池中没有多余的线程来执行任务,并且保存任务的多列也满了(指的是有界队列),对仍在提交给线程池的任务的处理策略。
public interface RejectedExecutionHandler {
/**
*当ThreadPoolExecutor的execut方法调用时,并且ThreadPoolExecutor不能接受一个任务Task时,该方法就有可能被调用。
* 不能接受一个任务Task的原因:有可能是没有多余的线程来处理,有可能是workqueue队列中没有多余的位置来存放该任务,该方法有可能抛出一个未受检的异常RejectedExecutionException
* @param r the runnable task requested to be executed
* @param executor the executor attempting to execute this task
* @throws RejectedExecutionException if there is no remedy
*/
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
该策略接口有四个实现类:
/**
* A handler for rejected tasks that throws a
* RejectedExecutionException.
*/
public static class AbortPolicy implements RejectedExecutionHandler {
/**
* Creates an AbortPolicy.
*/
public AbortPolicy() { }
/**
* Always throws RejectedExecutionException.
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
* @throws RejectedExecutionException always.
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException();
}
}
/**
* A handler for rejected tasks that silently discards the
* rejected task.
*/
public static class DiscardPolicy implements RejectedExecutionHandler {
/**
* Creates a DiscardPolicy.
*/
public DiscardPolicy() { }
/**
* Does nothing, which has the effect of discarding task r.
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
/**
* A handler for rejected tasks that discards the oldest unhandled
* request and then retries execute, unless the executor
* is shut down, in which case the task is discarded.
*/
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
/**
* Creates a DiscardOldestPolicy for the given executor.
*/
public DiscardOldestPolicy() { }
/**
* Obtains and ignores the next task that the executor
* would otherwise execute, if one is immediately available,
* and then retries execution of task r, unless the executor
* is shut down, in which case task r is instead discarded.
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}
/**
* A handler for rejected tasks that runs the rejected task
* directly in the calling thread of the {@code execute} method,
* unless the executor has been shut down, in which case the task
* is discarded.
*/
public static class CallerRunsPolicy implements RejectedExecutionHandler {
/**
* Creates a {@code CallerRunsPolicy}.
*/
public CallerRunsPolicy() { }
/**
* Executes task r in the caller's thread, unless the executor
* has been shut down, in which case the task is discarded.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
策略模式的优点:
策略模式的功能就是通过抽象、封装来定义一系列的算法,使得这些算法可以相互替换,所以为这些算法定义一个公共的接口,以约束这些算法的功能实现。如果这些算法具有公共的功能,可以将接口变为抽象类,将公共功能放到抽象父类里面。 策略模式的一系列算法是可以相互替换的、是平等的,写在一起就是if-else组织结构,如果算法实现里又有条件语句,就构成了多重条件语句,可以用策略模式,避免这样的多重条件语句。 扩展性更好:在策略模式中扩展策略实现非常的容易,只要新增一个策略实现类,然后在使用策略实现的地方,使用这个新的策略实现就好了。
策略模式的缺点:
策略模式的本质:
往期推荐
? ? ? ?