策略模式
策略模式(strategy)
什么是策略:
1. 可以实现目标的方案集合;
2. 根据形势发展而制定的行动方针和斗争方法;
3. 有斗争艺术,能注意方式方法。
定义:策略模式定义了一系列的算法,并将每一个算法封装起来,使每个算法可以相互替代,使算法本身和使用算法的客户端分割开来,相互独立。
结构:
1.策略接口角色IStrategy:用来约束一系列具体的策略算法,策略上下文角色ConcreteStrategy使用此策略接口来调用具体的策略所实现的算法。
2.具体策略实现角色ConcreteStrategy:具体的策略实现,即具体的算法实现。
3.策略上下文角色StrategyContext:策略上下文,负责和具体的策略实现交互,通常策略上下文对象会持有一个真正的策略实现对象,
策略上下文还可以让具体的策略实现从其中获取相关数据,回调策略上下文对象的方法。
背景
作为一名程序员,if-else应该都没少写,其实可以这么说,所有的代码都是用if-else堆起来的,只是分散在各个文件或者不同的方法中。其实前端调用不同的后端接口这也是if-else的体现,只是我们人为的把这些接口一一枚举出来了,平时的编码过程中,大多数人可能只注重代码功能的实现,都懒得对代码的可观性和优雅做什么处,也许是任务紧急或者任务中的情况,随着业务越来越复杂,if-else层级越来越长,后面优化的工作可能就越难。当然这是我们必然需要经历的过程,只有经历过才知道痛苦,才会去想办法怎么解决这种无线增加的嵌套,当有过实际的处理经验后,对以后遇到类似的情况自然而然就能相当处理方式
现实场景
有这样一个场景,前端页面有各种下拉框,下拉框中的值可能是数据库中查询出来的,也有可能是前端直接添加的,和后端约定好新添加的值对应的编码或者数字。不管是从后端数据库查询出来还是前端添加,每次新增一个新的值,当客户选择这个新的值并传递给后台服务器,后台服务器需要根据传递过来的值进行相应的处理,比如电商平台选择常见的支付方式,最开始可能会有微信支付,支付宝支付,京东支付,银联支付等,如果说某一天虚拟货币流行起来,可能就需要新增一种新的支付方式比特币支付,但是这种支付方式比较特殊,和之前的几种支付方式处理逻辑是完全不同或者需要各种校验,那按照一般的逻辑,我们可能需要重新在代码中继续添加if-else分支来应对新的变化,那如果后面再新增其他的支付方式,我们依然的新增更多的if-else。从程序设计的角度,这种方式破坏了开闭原则。那有没有一种好的方式呢?当然有,比如:策略设计模式
单个条件的if-else的情况(最理想的情况,一般很少会有单个条件做if中的判断条件)
最原始的方式:
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;
}
}
经过测试,上面的代码工作的很好,可是上面的代码是有问题的。上面存在的问题:把不同客户的报价的算法都放在了同一个方法里面,使得该方法很是庞大(现在是只是一个演示,所以看起来还不是很臃肿)。
下面看一下上面的改进,我们把不同客户的报价的算法都单独作为一个方法
改进1:算法和if-else分开
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客户的报价算法
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;
}
//对老客户的报价算法
private BigDecimal quoteOldCustomer(BigDecimal originalPrice) {
System.out.println("恭喜!老客户打9折");
originalPrice = originalPrice.multiply(new BigDecimal(0.9)).setScale(2,BigDecimal.ROUND_HALF_UP);
return originalPrice;
}
//对新客户的报价算法
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里面的代码很是麻烦
那有没有什么办法使得我们的报价管理即可扩展、可维护,又可以方便的响应变化呢?当然有解决方案啦,就是我们下面要讲的策略模式。
改进2:使用策略模式
策略模式:定义策略接口,由具体的策略类实现不同的算法,再由客户端选择符合自身的策略算法
步骤:
1.定义策略接口IStrategy:定义策略算法
2.创建策略工厂
3.具体策略实现策略接口ConcreteStrategy,并编写自己的具体算法实现,将当前策略放入策略工厂中
4.从策略工厂中选择对应的策略类
第1步:定义策略接口
public interface UserPayService extends InitializingBean {
/**
* 计算应付价格
* @param orderPrice
* @return
*/
BigDecimal quote(BigDecimal orderPrice);
}
第2步:创建策略工厂,并定义策略枚举类型
public class UserPayServiceStrategyFactory {
public static Map<String,UserPayService> map = new HashMap<>(16);
public static UserPayService getUserPayService(String userType){
Assert.notNull(userType,"userType can't be null");
return map.get(userType);
}
public static void register(UserTypeEnum userTypeEnum,UserPayService userPayService){
Assert.notNull(userTypeEnum,"userTypeEnum can't be null");
Assert.notNull(userPayService,"userPayService can't be null");
map.put(userTypeEnum.getUserType(),userPayService);
}
}
@Getter
@AllArgsConstructor
public enum UserTypeEnum {
/**
* 普通用户
*/
NORMAL("normal"),
/**
* vip用户
*/
VIP("vip"),
/**
* 超级vip用户
*/
SUPER_VIP("super_vip");
String userType;
}
第3步:各策略具体实现
//普通用户策略类
@Service
public class NormalUserPayServiceImpl implements UserPayService {
@Override
public BigDecimal quote(BigDecimal orderPrice) {
//普通用户 原价
return orderPrice.setScale(2,BigDecimal.ROUND_HALF_UP);
}
@Override
public void afterPropertiesSet() throws Exception {
UserPayServiceStrategyFactory.register(UserTypeEnum.NORMAL,this);
}
}
//vip用户策略类
@Service
public class VipUserPayServiceImpl implements UserPayService {
@Override
public BigDecimal quote(BigDecimal orderPrice) {
//vip用户 8折优惠
return orderPrice.multiply(new BigDecimal(0.8)).setScale(2,BigDecimal.ROUND_HALF_UP);
}
@Override
public void afterPropertiesSet() throws Exception {
UserPayServiceStrategyFactory.register(UserTypeEnum.VIP,this);
}
}
//超级vip用户策略类
@Service
public class SuperVipPayServiceImpl implements UserPayService {
@Override
public BigDecimal quote(BigDecimal orderPrice) {
//超级vip 5折优惠
return orderPrice.multiply(new BigDecimal(0.5)).setScale(2,BigDecimal.ROUND_HALF_UP);
}
@Override
public void afterPropertiesSet() throws Exception {
UserPayServiceStrategyFactory.register(UserTypeEnum.SUPER_VIP,this);
}
}
第4步:选择对应的策略
@RestController
@RequestMapping("/payService")
public class UserPayServiceController {
@GetMapping("/calculate")
public void calculate(){
//普通用户
BigDecimal normal = calPrice(new BigDecimal(100), "normal");
System.out.println("普通用户支付金额:"+normal);
//vip用户
BigDecimal vip = calPrice(new BigDecimal(100), "vip");
System.out.println("vip用户支付金额:"+vip);
//超级vip用户
BigDecimal svip = calPrice(new BigDecimal(100), "super_vip");
System.out.println("超级用户支付金额:"+svip);
}
public static BigDecimal calPrice(BigDecimal orderPrice, String userType) {
//从策略工厂中按照传入的参数获取具体某个策略
UserPayService strategy = UserPayServiceStrategyFactory.getUserPayService(userType);
return strategy.quote(orderPrice);
}
}
上面的代码明显比改进1要灵活,当添加新的用户类型,可以新增一个新的策略算法并放入策略工厂中,这样可以实现动态的拓展,能够满足任意添加用户类型而不用去修改原来的代码
符合开闭原则
策略模式在JDK源码中的使用 :线程池-拒绝策略
ThreadPoolExecutor.它有一个最终的构造函数如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
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:当线程池中没有多余的线程来执行任务,并且保存任务的多列也满了(指的是有界队列),对仍在提交给线程池的任务的处理策略。
RejectedExecutionHandler 是一个策略接口,用在当线程池中没有多余的线程来执行任务,并且保存任务的多列也满了(指的是有界队列),对仍在提交给线程池的任务的处理策略。
public interface RejectedExecutionHandler {
/**
* 当ThreadPoolExecutor的execut方法调用时,并且ThreadPoolExecutor不能接受一个任务Task时,该方法就有可能被调用。
* 不能接受一个任务Task的原因:有可能是没有多余的线程来处理,有可能是workqueue队列中没有多余的位置来存放该任务,
* 该方法有可能抛出一个未受检的异常RejectedExecutionException
*/
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
该策略接口有四个实现类:
1.AbortPolicy:该策略是直接将提交的任务抛弃掉,并抛出RejectedExecutionException异常;
2.DiscardPolicy:该策略也是将任务抛弃掉(对于提交的任务不管不问,什么也不做),不过并不抛出异常;
3.DiscardOldestPolicy:该策略是当执行器未关闭时,从任务队列workQueue中取出第一个任务,并抛弃这第一个任务,
进而有空间存储刚刚提交的任务。使用该策略要特别小心,因为它会直接抛弃之前的任务;
4.CallerRunsPolicy:该策略并没有抛弃任何的任务,由于线程池中已经没有了多余的线程来分配该任务,该策略是在当前线程(调用者线程)中直接执行该任务;
策略模式的优缺点
优点:
1.策略模式的功能就是通过抽象封装来定义一系列算法,使得这些算法可以相互替换
1.比if-else更加灵活
2.从容应对新的变化,只需要新增对应的策略即可
3.完全符合开闭原则,减少大量的if-else代码,使得代码看起来更加优雅
缺点:
1.每一种方式都需要有对应的策略类,
2.如果由客户端来选择策略,客户端必须清楚所有的策略类及其算法,才能做出正确的选择
3.随着策略类文件增多可能导致内存爆炸,
4.只适用于扁平的算法结构,对于多层级嵌套需要在各自的策略中再定义新的策略

浙公网安备 33010602011771号