小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
每个人可能都或多或少的办过会员,会员的好处就是会相对非会员能够获得更大的优惠,当然套路也比较多,就比如你想学门技能,花了很多钱办了个会员学习了一门技能,然后近期培训机构新推出了更优质的一门课,但面对不同会员买这门课给的折扣也是不一样的,你如果是普通会员就只给打8折,要是vip会员就打6折,不是会员就不打折,那么现在我们用代码实现这个场景。
public class Store {
public BigDecimal quote(BigDecimal bigDecimal, String type) {
if ("新会员".equals(type)) {
return newMember(bigDecimal);
} else if ("老会员".equals(type)) {
return oldMember(bigDecimal);
} else if ("vip会员".equals(type)) {
return vipMember(bigDecimal);
}
return null; }
//新会员不打折
public BigDecimal newMember(BigDecimal bigDecimal) {
return bigDecimal;
}
//老会员打八折
public BigDecimal oldMember(BigDecimal bigDecimal) {
return bigDecimal.multiply(new BigDecimal(0.8));
}
//vip会员打六折
public BigDecimal vipMember(BigDecimal bigDecimal)
{
return bigDecimal.multiply(new BigDecimal(0.6));
}}
通过创建一个打折方法,然后用if语句去判断不同用户要打多少折,但是如果我们又新增了一种会员,比如超级vip会员,那么我们不仅要对原有方法进行改动,多添加一个if判断,还要多写一个具体打折方法,这不符合开闭原则,无法灵活对需求的变更进行响应,所以就到了策略模式大显身手的时候了。
策略模式其思想是针对一组算法,将每一种算法都封装到具有共同接口的独立的类中,从而使它们可以相互替换。策略模式的最大特点是使得算法可以在不影响客户端的情况下发生变化,从而改变不同的功能。它是由抽象策略角色,策略具体角色,策略上下文组成。下面就给出类图和代码来进行描述。
#抽象策略类
public abstract class Strategy {
public abstract void AlgorithmInterface();
}
#具体策略类A
public class ConcreteStrategyA extends Strategy{
@Override
public void AlgorithmInterface() {
System.out.println("算法A实现方法");
}
}
#具体策略类B
public class ConcreteStrategyB extends Strategy{
@Override
public void AlgorithmInterface() {
System.out.println("算法B实现方法");
}
}
#策略上下文
public class Context {
private Strategy strategy;
public Context(Strategy strategy){
this.strategy=strategy;
}
//根据具体策略对象调用其算法
public void contextInterface(){
strategy.AlgorithmInterface();
}
}
通过类图和代码具体展现了策略抽象类,上下文,具体策略类三种的关系和代码实现。
-
策略抽象类:这是一个抽象的角色,通常情况下使用接口或者抽象类去实现。
-
具体策略类:包装了具体的算法和行为。
-
策略上下文:内部会持有一个抽象角色的引用,给客户端调用。
通过策略模式,我们将一开始会员打折的例子进行改造,不同类型的客户有不同的折扣,我们可以将不同类型的客户的报价规则都封装为一个独立的算法,然后抽象出这些报价算法的公共接口。
# 抽象策略打折类
public abstract class StoreStrategy {
public abstract BigDecimal quote(BigDecimal bigDecimal);
}
#具体策略非会员打折类
public class NewMember extends StoreStrategy {
@Override
public BigDecimal quote(BigDecimal bigDecimal) {
return bigDecimal;
}
}
#具体策略普通会员打折类
public class OldMember extends StoreStrategy {
@Override
public BigDecimal quote(BigDecimal bigDecimal) {
return bigDecimal.multiply(new BigDecimal(0.8));
}
}
#具体策略VIP会员打折类
public class VipMember extends StoreStrategy {
@Override
public BigDecimal quote(BigDecimal bigDecimal) {
return bigDecimal.multiply(new BigDecimal(0.6));
}
}
# 策略上下文
public class StoreContext {
StoreStrategy storeStrategy;
public StoreContext(StoreStrategy storeStrategy) {
this.storeStrategy = storeStrategy;
}
public BigDecimal quote(BigDecimal bigDecimal) {
return storeStrategy.quote(bigDecimal);
}
}
#测试类
public class StrategyTest {
public static void main(String[] args) {
ConcreteStrategyA concreteStrategyA=new ConcreteStrategyA();
Context context=new Context(concreteStrategyA);
context.contextInterface();
}
}
用策略模式改造后,可以看到即使后期加了新的会员或者有了新的打折方式,都只需要新建具体策略类继承抽象策略类即可实现,具体选用哪种策略只需让客户端进行选择,符合开闭原则。
策略模式其实在JDK中的应用也很广泛,例如在多线程编程中,我们有时候会经常用线程池来管理线程,从而减少因频繁创建和销毁线程造成的资源浪费,通常我们使用一个工厂类来创建线程池Executors,实际上Executors的内部使用的是类ThreadPoolExecutor.通过源码可知它有一个最终的构造函数如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
-
corePoolSize:线程池中的核心线程数量,即使这些线程没有任务,也不会将其销毁。
-
maximumPoolSize:线程池中的最多能够创建的线程数量。
-
keepAliveTime:当线程池中的线程数量大于corePoolSize时,多余的线程等待新任务的最长时间。
-
unit:keepAliveTime的时间单位。
-
workQueue:在线程池中的线程还没有还得及执行任务之前,保存任务的队列(当线程池中的线程都有任务在执行的时候,仍然有任务不断的提交过来,这些任务保存在workQueue队列中)。
-
RejectedExecutionHandler 是一个策略接口,用在当前线程池中没有多余的线程来执行任务,并且保存任务的多列也满了(指的是有界队列),对仍在提交给线程池的任务的处理策略。
public interface RejectedExecutionHandler {
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
该策略有四个实现类:
- AbortPolicy:该策略是直接将提交的任务抛弃掉,并抛出RejectedExecutionException异常。
public static class AbortPolicy implements RejectedExecutionHandler {
public AbortPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
-
DiscardPolicy:该策略也是将任务抛弃(对于提交的任务不管不问,什么也不做),不过并不抛出异常。
public static class DiscardPolicy implements RejectedExecutionHandler {
public DiscardPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
- DiscardOldestPolicy:该策略是当执行器未关闭时,从任务队列workQueue中取出第一个任务,并抛弃这第一个任务,进而有空间存储刚刚提交的任务。使用该策略要特别小心,因为它会直接抛弃之前的任务。
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
public DiscardOldestPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}
- CallerRunsPolicy:该策略并没有抛弃任何的任务,由于线程池中已经没有了多余的线程来分配该任务,该策略是在当前线程(调用者线程)中直接执行该任务。
public static class CallerRunsPolicy implements RejectedExecutionHandler {
public CallerRunsPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
-
eadPoolExecutor通过持有RejectedExecutionHandler接口的引用,以便在构造函数中可以由客户端自己制定具体的策略并注入。
策略模式其实并不是说让我们来如何实现算法,而是如何进行调用和组织这些算法,从而让我们的代码更加的灵活,可扩展。
策略模式的一系列算法是可相互替换的,写在一起的话其实就是一个if,else结构,如果算法里有条件语句,就可以用策略模式来避免这样的条件语句。
如果你觉得文章还不错,就请关注我吧~