1、策略模式
策略模式定义了一系列的算法,并将每一个算法封装起来,使每个算法可以相互替代,使算法本身和使用算法的客户端分割开来,相互独立。
策略模式的角色:
-
策略接口角色IStrategy:用来约束一系列具体的策略算法,策略上下文角色ConcreteStrategy使用此策略接口来调用具体的策略所实现的算法。
-
具体策略实现角色ConcreteStrategy:具体的策略实现,即具体的算法实现。
-
策略上下文角色StrategyContext:策略上下文,负责和具体的策略实现交互,通常策略上下文对象会持有一个真正的策略实现对象,策略上下文还可以让具体的策略实现从其中获取相关数据,回调策略上下文对象的方法。
//策略接口
public interface IStrategy {
//定义的抽象算法方法 来约束具体的算法实现方法
public void algorithmMethod();
}
// 具体的策略实现1
public class ConcreteStrategy1 implements IStrategy {
//具体的算法实现
@Override
public void algorithmMethod() {
System.out.println("this is ConcreteStrategy1 method...");
}
}
// 具体的策略实现2
public class ConcreteStrategy2 implements IStrategy {
//具体的算法实现
@Override
public void algorithmMethod() {
System.out.println("this is ConcreteStrategy2 method...");
}
}
/**
* 策略上下文
*/
public class StrategyContext {
//持有一个策略实现的引用
private IStrategy strategy;
//使用构造器注入具体的策略类
public StrategyContext(IStrategy strategy) {
this.strategy = strategy;
}
public void contextMethod(){
//调用策略实现的方法
strategy.algorithmMethod();
}
}
//外部客户端
public class Client {
public static void main(String[] args) {
//1.创建具体测策略实现
IStrategy strategy = new ConcreteStrategy2();
//2.在创建策略上下文的同时,将具体的策略实现对象注入到策略上下文当中
StrategyContext ctx = new StrategyContext(strategy);
//3.调用上下文对象的方法来完成对具体策略实现的回调
ctx.contextMethod();
}
}
示例
现实生活中我们到商场买东西的时候,卖场往往根据不同的客户制定不同的报价策略,比如针对新客户不打折扣,针对老客户打9折,针对VIP客户打8折…
//一般做法
//商城管理
import java.math.BigDecimal;
public class QuoteManager {
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;
}
}
//客户
public class Client {
public static void main(String[] args) {
QuoteManager quoteManager = new QuoteManager();
BigDecimal price = quoteManager.quote(BigDecimal.valueOf(780.09), "VIP客户");
System.out.println("支付: " + price + "元");
}
}
这种方式下:
1、当我们新增一个客户类型的时候,首先要添加一个该种客户类型的报价算法方法,然后再quote方法中再加一个else if的分支,是不是感觉很是麻烦呢?而且这也违反了设计原则之一的开闭原则(open-closed-principle)
2.我们经常会面临这样的情况,不同的时期使用不同的报价规则,比如在各个节假日举行的各种促销活动时、商场店庆时往往都有普遍的折扣,但是促销时间一旦过去,报价就要回到正常价格上来。按照上面的代码我们就得修改if else里面的代码很是麻烦
策略模式:
//报价策略接口
public interface IQuoteStrategy {
//获取折后价的价格
BigDecimal getPrice(BigDecimal originalPrice);
}
//新客户的报价策略实现类
public class NewCustomerQuoteStrategy implements IQuoteStrategy {
@Override
public BigDecimal getPrice(BigDecimal originalPrice) {
System.out.println("抱歉!新客户没有折扣!");
return originalPrice;
}
}
//老客户的报价策略实现
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;
}
}
//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;
}
}
//报价上下文角色
public class QuoteContext {
//持有一个具体的报价策略
private IQuoteStrategy quoteStrategy;
//注入报价策略
public QuoteContext(IQuoteStrategy quoteStrategy){
this.quoteStrategy = quoteStrategy;
}
//回调具体报价策略的方法
public BigDecimal getPrice(BigDecimal originalPrice){
return quoteStrategy.getPrice(originalPrice);
}
}
//外部客户端
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);
}
}
策略模式下,新增客户类型或者修改原有客户类型,只需要新增一个实现类或者修改对应类即可
2、策略模式和工厂模式区别
策略模式和工厂模式在模式结构上,两者很相似:
区别:
用途和关注点
策略模式属于行为类设计模式,关注对行为的封装,client注重的是结果
工厂模式属于创建类设计模式,关注对象的创建,client注重的是获取类对象
解决问题:
策略模式是为了解决的是策略的切换与扩展,更简洁的说是定义策略族,分别封装起来,让他们之间可以相互替换,策略模式让策略的变化独立于使用策略的客户。
工厂模式是创建型的设计模式,它接受指令,创建出符合要求的实例;它主要解决的是资源的统一分发,将对象的创建完全独立出来,让对象的创建和具体的使用客户无关。主要应用在多数据库选择,类库文件加载等。
工厂相当于黑盒子,策略相当于白盒子;
3、策略模式和模板模式区别
策略模式(Strategy Pattern)和模板方法模式(Template Method Pattern)是两种不同的行为类设计模式,它们在实现上有一些明显的区别
- 目的和应用场景:
- 策略模式: 主要用于定义一系列的算法,将每个算法封装起来,并使它们可以互相替换。客户端可以选择不同的策略对象,以达到不同的行为。
- 模板方法模式: 主要用于定义一个算法的骨架,将一些步骤的实现延迟到子类。父类中定义了模板方法,该方法中的某些步骤的具体实现由子类决定。
- 关注点:
- 策略模式: 关注的是算法的替换和客户端的选择。
- 模板方法模式: 关注的是算法的骨架和具体步骤的延迟实现。
- 组成结构:
- 策略模式: 主要包含环境类(Context)、策略接口(Strategy)和具体策略实现类(ConcreteStrategy)。
- 模板方法模式: 主要包含抽象类(AbstractClass)、模板方法(Template Method)和具体实现步骤的方法。
- 灵活性和扩展性:
- 策略模式: 策略可以相对独立地变化,客户端可以灵活地选择和切换不同的策略。
- 模板方法模式: 算法的骨架是固定的,但某些步骤的具体实现可以在子类中进行扩展。
- 调用方式:
- 策略模式: 客户端通常主动选择并设置具体的策略对象。
- 模板方法模式: 算法的执行是由父类的模板方法触发的,子类可以通过扩展来影响某些步骤的具体实现。
策略模式关注的是定义一系列算法并使它们可以互相替换,而模板方法模式关注的是定义一个算法的骨架,将某些步骤的实现交给子类决定。它们分别适用于不同的设计需求和场景。
4、spring中的策略模式
4.1 ThreadPoolExecutor
在多线程编程中,我们经常使用线程池来管理线程,以减缓线程频繁的创建和销毁带来的资源的浪费,在创建线程池的时候,经常使用一个工厂类来创建线程池Executors,实际上Executors的内部使用的是类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
* @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);
}
该策略接口有四个实现类
AbortPolicy:该策略是直接将提交的任务抛弃掉,并抛出RejectedExecutionException异常。
java复制代码/**
* A handler for rejected tasks that throws a
* <tt>RejectedExecutionException</tt>.
*/
public static class AbortPolicy implements RejectedExecutionHandler {
/**
* Creates an <tt>AbortPolicy</tt>.
*/
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();
}
}
DiscardPolicy:该策略也是将任务抛弃掉(对于提交的任务不管不问,什么也不做),不过并不抛出异常。
java复制代码/**
* A handler for rejected tasks that silently discards the
* rejected task.
*/
public static class DiscardPolicy implements RejectedExecutionHandler {
/**
* Creates a <tt>DiscardPolicy</tt>.
*/
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) {
}
}
DiscardOldestPolicy:该策略是当执行器未关闭时,从任务队列workQueue中取出第一个任务,并抛弃这第一个任务,进而有空间存储刚刚提交的任务。使用该策略要特别小心,因为它会直接抛弃之前的任务。
java复制代码/**
* A handler for rejected tasks that discards the oldest unhandled
* request and then retries <tt>execute</tt>, unless the executor
* is shut down, in which case the task is discarded.
*/
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
/**
* Creates a <tt>DiscardOldestPolicy</tt> 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);
}
}
}
CallerRunsPolicy:该策略并没有抛弃任何的任务,由于线程池中已经没有了多余的线程来分配该任务,该策略是在当前线程(调用者线程)中直接执行该任务。
java复制代码/**
* 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();
}
}
}
类ThreadPoolExecutor中持有一个RejectedExecutionHandler接口的引用,以便在构造函数中可以由外部客户端自己制定具体的策略并注入。下面看一下其类图:
创建线程池的时候,根据具体场景,选择不同的拒绝策略
4.2 InstantiationStrategy
spring中bean的实例化过程如下:
InstantiationStrategy
提供了实例化bean的不同策略:
public interface InstantiationStrategy {
/**
* 默认的构造方法
* @param bd
* @param beanName
* @param owner
* @return
* @throws BeansException
*/
Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner)throws BeansException;
/**
* 指定构造方法
* @param bd
* @param beanName
* @param owner
* @param ctor
* @param args
* @return
* @throws BeansException
*/
Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
Constructor<?> ctor, Object... args) throws BeansException;
/**
* 通过指定的工厂方法
* @param bd
* @param beanName
* @param owner
* @param factoryBean
* @param factoryMethod
* @param args
* @return
* @throws BeansException
*/
Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
@Nullable Object factoryBean, Method factoryMethod, Object... args)
throws BeansException;
看具体的方法有子类实现并执行,类图如下:
使用策略模式,实例化bean的时候,根据传参的不同,选择不同的实例化策略
具体InstantiationStrategy实现及执行流程,搜索[相关文章](