1. 程式人生 > >java策略設計模式

java策略設計模式

false 本地 cor 構造器 run方法 mman 查看 們的 ola

1、概述

策略模式定義了一系列的算法,並將每一個算法封裝起來,而且使他們可以相互替換,讓算法獨立於使用它的客戶而獨立變化。

實際上不要被晦澀難懂的定義迷惑,其實策略設計模式就是定義一個接口,那麽只要實現該接口的類,對這些方法進行實現,那麽不同的實現類就完成了不同的算法邏輯,而使用該接口的地方,可以根據需要隨意更改實現類,因為它們的接口一樣額。

因此策略設計模式有三個角色:

抽象策略(Strategy)角色:這是一個抽象角色,通常由一個接口實現。此角色給出所有的具體策略類所需的接口。

具體策略(ConcreteStrategy)角色:包裝了相關的算法或行為。

策略上下文(Context)角色:持有一個Strategy的引用。

2、代碼示例

首先定一個算法接口。

package com.yefengyu.pattern.strategy;

public interface Strategy
{
    String 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("======");
    }
}

編寫客戶端來演示:

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 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一個對象,然後調用execute(submit方法實際稍有點復雜,主要是對任務進行封裝,再調了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方法,該方法如下,實際就是調用了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);
}

有了接口,那麽實現類在哪裏呢?還是在RejectedExecutionHandler中,只是使用了靜態內部類的形式:

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): 本章節的第一處代碼: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(); 
 

start0是本地方法,最後還是調用了Thread的run方法

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

(d): 客戶端使用? ---> public Thread(Runnable target) 使用構造方法傳入,調用start方法啟動線程,可見start方法調用了run方法。

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

thread.start();

通過上面的分析,我們平常使用的線程也使用了策略設計模式,只是這裏的策略具體實現交由程序員實現,JDK為我們提供了策略接口、策略上下文等。

5、總結

策略設計模式一般實現步驟如下:

a、編寫策略接口

b、編寫策略實現類

c、編寫策略執行上下文,一般這個類持有一個策略接口屬性,通過構造函數為其賦值,並且該類有一個主要的入口函數,該函數有很多個操作步驟,其中某一個步驟要使用算法(也就是接口方法)。

d、客戶端在使用的時候,首先使用策略執行上下文這個類的構造函數傳入策略實現類,接著調用策略執行上下文這個類的主要入口函數,就可以執行到算法。通過構造函數傳入不同的策略實現類,就可以更換程序的算法邏輯。

策略設計模式缺點就是客戶端需要知道所有的策略實現類。

java策略設計模式