1、概述

策略模式定义了一系列的算法,并将每一个算法封装起来,而且使他们可以相互替换,让算法独立于使用它的客户而独立变化。

其实不要被晦涩难懂的定义所迷惑,策略设计模式实际上就是定义一个接口,只要实现该接口,并对接口的方法进行实现,那么不同的实现类就完成了不同的算法逻辑,而使用该接口的地方,则可以根据需要随意更改实现类,因为它们的接口一样额。

因此策略设计模式有三个角色:

抽象策略(Strategy)角色:这是一个抽象角色,通常由一个接口实现。此角色给出所有的具体策略类所需的接口。

具体策略(ConcreteStrategy)角色:包装了相关的算法或行为。

策略上下文(Context)角色:持有一个Strategy的引用,并且在某处调用了算法。

注意:算法定义其实就是接口的方法,算法具体逻辑就是接口实现类中对方法的具体实现。 


2、代码示例

首先定义一个算法接口。Strategy接口的run方法就是一种算法的定义。

package com.yefengyu.pattern.strategy;

public interface Strategy
{
String run();
}

其次,编写两个类实现Strategy接口,每个实现类都重写run方法(下面只是简单的返回)。实际上不同实现类的run方法是某种算法(业务领域)的不同实现。

package com.yefengyu.pattern.strategy;

public class ConcreteStrategyOne implements Strategy
{
@Override
public String run()
{
       //此处只是简单的返回
        return "ConcreteStrategy  One 实现的算法";
}
}
package com.yefengyu.pattern.strategy;

public class ConcreteStrategyTwo implements Strategy
{
@Override
public String run()
{
//此处只是简单的返回
return "ConcreteStrategy Two 实现的算法";
}
}

接着编写一个策略上下文角色,它持有Strategy接口。下面的StrategyContext类就是策略上下文角色,它有一个Strategy类型的属性,并且通过构造函数传入(也可以是其它方式)。在StrategyContext中有一个execute方法,该方法我们想象它是很复杂的,在其中某一步使用到了策略算法。

package com.yefengyu.pattern.strategy;

public class StrategyContext
{
private Strategy strategy; public StrategyContext(Strategy strategy)
{
this.strategy = strategy;
} public void execute()
{
//StrategyContext 的 execute 有许多事情要做
System.out.println("------"); //天啊,终于执行到算法这里了
String result = strategy.run();
System.out.println(result); //后续还有好多操作。。。
System.out.println("======");
}
}

编写客户端来演示:生成两个不同的Strategy的实现类的对象,分别通过构造函数传入到策略上下文角色中,StrategyContext通过执行execute就调用到具体的算法(run)。

package com.yefengyu.pattern.strategy;

public class Client
{
public static void main(String[] args)
{
Strategy strategy = new ConcreteStrategyOne();
StrategyContext strategyContext = new StrategyContext(strategy);
strategyContext.execute(); strategy = new ConcreteStrategyTwo();
strategyContext = new StrategyContext(strategy);
strategyContext.execute();
}
}

上面的代码中,StrategyContext通过构造函数传入不同的实现类,即可在其execute方法中调用不同的算法。


3、JDK中的策略设计模式-ThreadPoolExecutor

ThreadPoolExecutor使用方式如下:首先定义一个ThreadPoolExecutor对象,通过调用execute方法来执行,注意execute方法需要传入Runnable。

ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(), new ThreadPoolExecutor.CallerRunsPolicy()); executor.execute(new Runnable()
{
@Override
public void run()
{
System.out.println("hello ThreadPoolExecutor");
}
});
    找到ThreadPoolExecutor的源码,来看看ThreadPoolExecutor是如何使用策略设计模式的。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.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
     构造器的其它内容无需关注。只需要注意构造器的最后一个参数和代码的最后一行,就可以明白ThreadPoolExecutor有一个RejectedExecutionHandler 类型的成员变量 handler,而构造函数则为其赋值。
    线程池一般先new一个ThreadPoolExecutor对象,然后调用execute方法执行任务,因此我们看看execute方法的实现。
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException(); int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
    上面的代码无需仔细看,注意代码中多次调用了reject方法,该方法源码如下,实际就是调用了ThreadPoolExecutor类中RejectedExecutionHandler类型的成员变量handler的方法。
final void reject(Runnable command)
{
handler.rejectedExecution(command, this);
}
    查看下RejectedExecutionHandler发现它是一个接口,只有一个方法。
package java.util.concurrent;

public interface RejectedExecutionHandler
{
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

有了接口,那么实现类在哪里呢?还是在ThreadPoolExecutor类中,只是使用了静态内部类的形式:

public static class CallerRunsPolicy implements RejectedExecutionHandler {

        public CallerRunsPolicy() { }

        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
} 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());
}
} public static class DiscardPolicy implements RejectedExecutionHandler { public DiscardPolicy() { } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
} public static class DiscardOldestPolicy implements RejectedExecutionHandler { public DiscardOldestPolicy() { } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}

经过上面的一系列源码探究,我终于要使出对比大法了。

(a): RejectedExecutionHandler接口中的rejectedExecution就是策略模式中的算法,此处算法实际叫做拒绝策略:如果线程数量大于等于 maximumPoolSize,且 workQueue 已满,则使用拒绝策略处理新任务。

(b): CallerRunsPolicy 、AbortPolicy 、AbortPolicy 、DiscardOldestPolicy 是拒绝策略算法的具体实现。和上面的演示示例相比,此处直接在策略上下文环境直接使用静态内部类的方式实现了抽象的策略,也就是接口中算法的定义。

(c): ThreadPoolExecutor就是策略的上下文(Context)角色。它持有一个RejectedExecutionHandler的引用,使用构造函数对其赋值,其中有方法execute作为环境上下文的主要入口,并且调用了算法。

(d): 客户端:见本节最上面的代码,在new ThreadPoolExecutor对象的时候可以传入拒绝策略,在使用ThreadPoolExecutor时可以通过传入不同的拒绝策略来到达不同的效果,正是策略模式期望的效果。


4、JDK中的策略设计模式-Thread与Runnable

特别注意:在https://www.cnblogs.com/yefengyu/p/10520531.html文章中,我讲解了Thread是模板设计模式的一种实现,那里前提是使用Thread方式创建线程,而不是使用Runnable方式。

通过上面线程池的对比试验之后,这次分析换一种思路,我们现在把模板设计模式往Thread和Runnable身上套。

(a): 策略算法、也就是抽象策略角色:Runnable接口及其方法run方法。

@FunctionalInterface
public interface Runnable
{
public abstract void run();
}

(b): 算法具体实现        

一般由程序员创建一个类实现Runnable接口,重写run方法。

package com.yefengyu.pattern.strategy;

public class MyRunnable implements Runnable
{
@Override
public void run()
{
System.out.println("hello Runnable ");
}
}

(c): 策略上下文  

    Thread类持有Runnable接口,并且通过构造函数传入。start方法中隐式调用了Thread类的run方法。

持有抽象策略接口

private Runnable target;

通过构造函数为抽象策略接口赋值

public Thread(Runnable target)
{
init(null, target, "Thread-" + nextThreadNum(), 0);
}

策略上下文一般都有一个主要的方法入口,在方法的某一步调用了算法。

public synchronized void start() {

    if (threadStatus != 0)
throw new IllegalThreadStateException(); group.add(this); boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
} private native void start0();
 

start方法是主要的方法入口,它调用了start0方法,而start0是本地方法,最后还是调用了Thread的run方法(细节自行分析),而Thread的run方法调用了Runnable的run方法。也就是最终调用到了算法。

    @Override
public void run() {
if (target != null) {
target.run();
}
}

(d): 客户端使用

public Thread(Runnable target)  使用构造方法传入,调用start方法启动线程。

Thread thread = new Thread(new MyRunnable());

thread.start();

通过上面的分析,我们平常使用的线程也使用了策略设计模式,只是这里的策略具体实现交由程序员实现,JDK为我们提供了策略接口、策略上下文等。


5、总结

策略设计模式一般实现步骤如下:

a、编写策略接口

b、编写策略实现类

c、编写策略执行上下文,一般这个类持有一个策略接口属性,通过构造函数为其赋值,并且该类有一个主要的入口函数,该函数有很多个操作步骤,其中某一个步骤要使用算法(也就是接口方法)。

d、客户端在使用的时候,首先使用策略执行上下文这个类的构造函数传入策略实现类,接着调用策略执行上下文这个类的主要入口函数,就可以执行到算法。通过构造函数传入不同的策略实现类,就可以更换程序的算法逻辑。

策略设计模式缺点就是客户端需要知道所有的策略实现类。

java策略设计模式的更多相关文章

  1. java 策略设计模式

    在软件开发中也常常遇到类似的情况,实现某一个功能有多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能.如查找.排序等,一种常用的方法是硬编码(Hard Coding)在 ...

  2. Java(Android)编程思想笔记02:组合与继承、final、策略设计模式与适配器模式、内部类、序列化控制(注意事项)

    1.组合和继承之间的选择 组合和继承都允许在新的类中放置子对象,组合是显式的这样做,而继承则是隐式的做. 组合技术通常用于想在新类中使用现有类的功能而非它的接口这种情形.即在新类中嵌入某个对象,让其实 ...

  3. JAVA学习笔记--策略设计模式与适配器模式

    一.策略设计模式 创建一个能够根据所传递对象的不同而具有不同行为的方法被称为策略设计模式:这类方法包含所要执行的算法中固定不变的部分,而“策略”包含变化的部分.策略就是传递进去的参数对象,它包含要执行 ...

  4. Java设计模式之策略设计模式

    1.什么是-策略设计模式 在软件开发中常常遇到这种情况,实现某一个功能有多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能.如查找.排序等,一种常用的方法是硬编码(Ha ...

  5. 基于java的设计模式入门(1)——为什么要学习设计模式

    大年初一,楼主在这里给大家拜年,祝大家码上升职加薪,码上有对象结婚,码上有车有房,幸福安康. 过完年,回学校注册报道之后,大概就要回深圳到公司开始实习了.提高自己,无非就有两种方式,一是看书学习,二是 ...

  6. Head First设计模式——策略设计模式

    策略设计模式 说在前面的话 入软件一年啦,平心而论,总算不限于只会钻研些基础的语言语法了,数据结构和算法也恶补的差不多了.所以~趁着现在一边实习一边啃<Head First设计模式>的功夫 ...

  7. Java经典设计模式之十一种行为型模式(附实例和详解)

    Java经典设计模式共有21中,分为三大类:创建型模式(5种).结构型模式(7种)和行为型模式(11种). 本文主要讲行为型模式,创建型模式和结构型模式可以看博主的另外两篇文章:Java经典设计模式之 ...

  8. Java的设计模式

    一.什么是设计模式: 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了可重用代码.让代码更容易被他人理解.保证代码可靠性. ...

  9. Java EE设计模式(主要简单介绍工厂模式,适配器模式和模板方法模式)

    Java EE设计模式分为三种类型,共23种: 创建型模式:单例模式.抽象工厂模式.建造者模式.工厂模式.原型模式. 结构型模式:适配器模式.桥接模式.装饰模式.组合模式.外观模式.享元模式.代理模式 ...

随机推荐

  1. (译)MySQL的10个基本性能技巧

    原文出处:https://www.infoworld.com/article/3210905/sql/10-essential-performance-tips-for-mysql.html MySQ ...

  2. 前端页面JS和CSS以及图片加载nginx报错:net::ERR_CONTENT_LENGTH_MISMATCH的解决与检查

    首先检查nginx权限 具体可参考地址https://www.cnblogs.com/hooly/p/9951748.html 或者百度其他方法 还有种情况,之前是可以用的,突然出现这种加载报错的情况 ...

  3. JS中this的四种用法

    1.在一般函数方法中使用 this 指代全局对象 2.作为对象方法调用,this 指代上级对象 3.作为构造函数调用,this 指代new 出的对象 4.apply 调用 ,apply方法作用是改变函 ...

  4. 什么是jquery

    框架就是一个半成品,不能直接使用,需要加工处理后才可使用

  5. 【原创】控制perl和python脚本执行过程中脚本文件是否关闭的方法

    引子 跟踪perl和python脚本对文件的访问,实际过程中,perl和python解析器在解析完脚本后,直接关闭了 脚本文件,在进程中查询不到是访问文件的脚本文件名称. shell.perl和pyt ...

  6. haproxy监控页面添加及参数简介(转)

    环境: [root@localhost 13:55:31 haproxy]# cat /etc/redhat-release CentOS release 6.8 (Final) [root@loca ...

  7. 深入理解C++11【2】

    [深入理解C++11[2]] 1.继承构造函数. 当基类拥有多个构造函数的时候,子类不得不一一实现. C++98 可以使用 using 来使用基类的成员函数. #include < iostre ...

  8. vue项目结构搭建

    1安装node.js,已集成npm 2.临时使用淘宝镜像 npm --registry https://registry.npm.taobao.org install express 3.instal ...

  9. 【搬运】 Page Object 官方文档 (新增了Widget特性)

    Appium Java client has facilities which components to [Page Object](https://github.com/SeleniumHQ/se ...

  10. scrapy meta信息丢失

    在做58同城爬二手房时,由于房产详情页内对价格进行了转码处理,所以只能从获取详情页url时同时获取该url对应房产的价格,并通过meta传递给下回调函数 现在问题是,在回调函数中找不到原函数meta信 ...