策略模式原來就這麼簡單!

Java3y發表於2018-12-24

前言

只有光頭才能變強

無論是面試還是個人的提升,設計模式是必學的。今天來講解策略模式~

一、策略模式介紹

我一次聽到策略模式這個詞,是在我初學JDBC的時候。不知道大家有沒有用過DBUtils這個元件。當時初學跟著視訊學習,方立勳老師首先是讓我們先自己封裝一下JDBC的一些常用的操作(實際上就是模仿DBUtils這個元件)。

當時候的問題是這樣的:我們打算封裝一下 query()查詢方法,傳入的引數有 Stringsql,Object[]objects(指定SQL語句和對應的引數)。我們想根據不同的業務返回不同的值。

  • 比如說,有的時候我們返回的是一條資料,那我們想將這條資料封裝成一個Bean物件

  • 比如說,有的時候我們返回的是多條資料,那我們想將這多條資料封裝成一個 List<Bean> 集合

  • 比如說,有的時候我們返回的是xxxx資料,那我們想將這多條資料封裝成一個 Map<Bean> 集合

  • ........等等等

當時解決方案是這樣的:

  • 先定義一個介面:ResultSetHandler(呼叫者想要對結果集進行什麼操作,只要實現這個介面即可)

    • 這個介面定義了行為。 Objecthanlder(ResultSetresultSet);

  • 然後實現上面的介面,比如我們要封裝成一個Bean物件,就是 publicclassBeanHandlerimplementsResultSetHandler

  • 呼叫的時候,實際上就是 query()查詢方法多一個引數 query(Stringsql,Object[]objects,ResultSetHandlerrsh)。呼叫者想要返回什麼型別,只要傳入相對應的ResultSetHandler實現類就是了。

程式碼如下:

  1.    query方法:


  2.    //這個方法的返回值是任意型別的,所以定義為Object。

  3.    public static Object query(String sql, Object[] objects, ResultSetHandler rsh) {

  4.        Connection connection = null;

  5.        PreparedStatement preparedStatement = null;

  6.        ResultSet resultSet = null;

  7.        try {

  8.            connection = getConnection();

  9.            preparedStatement = connection.prepareStatement(sql);


  10.            //根據傳遞進來的引數,設定SQL佔位符的值

  11.            if (objects != null) {

  12.                for (int i = 0; i < objects.length; i++) {

  13.                    preparedStatement.setObject(i + 1, objects[i]);

  14.                }

  15.            }

  16.            resultSet = preparedStatement.executeQuery();


  17.            //呼叫呼叫者傳遞進來實現類的方法,對結果集進行操作

  18.            return rsh.hanlder(resultSet);

  19.    }

  20.    介面:


  21.    /*

  22.    * 定義對結果集操作的介面,呼叫者想要對結果集進行什麼操作,只要實現這個介面即可

  23.    * */

  24.    public interface ResultSetHandler {

  25.         Object hanlder(ResultSet resultSet);

  26.    }


  27.    介面實現類(Example):


  28.    //介面實現類,對結果集封裝成一個Bean物件

  29.    public class BeanHandler implements ResultSetHandler {

  30.        //要封裝成一個Bean物件,首先要知道Bean是什麼,這個也是呼叫者傳遞進來的。

  31.        private Class clazz;

  32.        public BeanHandler(Class clazz) {

  33.            this.clazz = clazz;

  34.        }

  35.        @Override

  36.        public Object hanlder(ResultSet resultSet) {

  37.            try {

  38.                //建立傳進物件的例項化

  39.                Object bean = clazz.newInstance();

  40.                if (resultSet.next()) {

  41.                    //拿到結果集後設資料

  42.                    ResultSetMetaData resultSetMetaData = resultSet.getMetaData();

  43.                    for (int i = 0; i < resultSetMetaData.getColumnCount(); i++) {

  44.                        //獲取到每列的列名

  45.                        String columnName = resultSetMetaData.getColumnName(i+1);

  46.                        //獲取到每列的資料

  47.                        String columnData = resultSet.getString(i+1);

  48.                        //設定Bean屬性

  49.                        Field field = clazz.getDeclaredField(columnName);

  50.                        field.setAccessible(true);

  51.                        field.set(bean,columnData);

  52.                    }

  53.                    //返回Bean物件

  54.                    return bean;

  55.                }

這就是策略模式??就這??這不是多型的使用嗎??

1.1策略模式講解

《設計模式之禪》:

定義一組演算法,將每個演算法都封裝起來,並且使他們之間可以互換

策略模式的類圖是這樣的:

策略模式原來就這麼簡單!

策略的介面和具體的實現應該很好理解:

  • 策略的介面相當於我們上面所講的ResultSetHandler介面(定義了策略的行為)

  • 具體的實現相當於我們上面所講的BeanHandler實現(介面的具體實現)

    • 具體的實現一般還會有幾個,比如可能還有ListBeanHandler、MapBeanHandler等等

令人想不明白的可能是:策略模式還有一個Context上下文物件。這物件是用來幹什麼的呢?

《設計模式之禪》:

Context叫做上下文角色,起承上啟下封裝作用,遮蔽高層模組對策略、演算法的直接訪問,封裝可能存在的變化。

在知乎上也有類似的問題(為什麼不直接呼叫,而要通過Person?):

策略模式原來就這麼簡單!

說白了,通過Person來呼叫更符合物件導向(遮蔽了直接對具體實現的訪問)。

首先要明白一個道理,就是——到底是 “人” 旅遊,還是火車、汽車、自行車、飛機這些交通工具旅遊?

如果沒有上下文的話,客戶端就必須直接和具體的策略實現進行互動了,尤其是需要提供一些公共功能或者是儲存一些狀態的時候,會大大增加客戶端使用的難度;引入上下文之後,這部分工作可以由上下文來完成,客戶端只需要和上下文進行互動就可以了。這樣可以讓策略模式更具有整體性,客戶端也更加的簡單

具體的連結:

  • https://www.zhihu.com/question/31162942

所以我們再說回上文的通用類圖,我們就可以這樣看了:

策略模式原來就這麼簡單!

1.2策略模式例子

現在3y擁有一個公眾號,名稱叫做Java3y。3y想要這讓更多的人認識到Java3y這個公眾號。所以每天都在想怎麼漲粉(hahah

於是3y就開始想辦法了(操碎了心),同時3y在這一段時間下來發現漲粉的方式有很多。為了方便,定義一個通用的介面方便來管理和使用唄。

介面:

  1. /**

  2. * 增加粉絲策略的介面(Strategy)

  3. */

  4. interface IncreaseFansStrategy {

  5.    void action();

  6. }

漲粉的具體措施,比如說,請水軍:

  1. /**

  2. * 請水軍(ConcreteStrategy)

  3. */

  4. public class WaterArmy implements IncreaseFansStrategy {

  5.    @Override

  6.    public void action() {

  7.        System.out.println("3y牛逼,我要給你點贊、轉發、加雞腿!");

  8.    }

  9. }

漲粉的具體措施,比如說,認真寫原創:

  1. /**

  2. * 認真寫原創(ConcreteStrategy)

  3. */

  4. public class OriginalArticle implements IncreaseFansStrategy{


  5.    @Override

  6.    public void action() {

  7.        System.out.println("3y認真寫原創,最新一篇文章:《策略模式,就這?》");

  8.    }

  9. }

3y還想到了很多漲粉的方法,比如說送書活動啊、商業互吹啊等等等...(這裡就不細說了)

說到底,無論是哪種漲粉方法,都是通過3y去執行的。

  1. /**

  2. * 3y(Context)

  3. */

  4. public class Java3y {

  5.    private IncreaseFansStrategy strategy ;


  6.    public Java3y(IncreaseFansStrategy strategy) {

  7.        this.strategy = strategy;

  8.    }

  9.    // 3y要發文章了(買水軍了、送書了、寫知乎引流了...)。

  10.    // 具體執行哪個,看3y選哪個

  11.    public void exec() {

  12.        strategy.action();

  13.    }

  14. }

所以啊,每當到了發推文的時候,3y就可以挑用哪種方式漲粉了:

  1. public class Main {

  2.    public static void main(String[] args) {

  3.        // 今天2018年12月24日

  4.        Java3y java3y = new Java3y(new WaterArmy());

  5.        java3y.exec();

  6.        // 明天2018年12月25日

  7.        Java3y java4y = new Java3y(new OriginalArticle());

  8.        java4y.exec();

  9.        // ......

  10.    }

  11. }

執行結果:

策略模式原來就這麼簡單!

1.3策略模式優缺點

優點:

  • 演算法可以自由切換

    • 改一下策略很方便

  • 擴充套件性良好

    • 增加一個策略,就多增加一個類就好了。

缺點:

  • 策略類的數量增多

    • 每一個策略都是一個類,複用的可能性很小、類數量增多

  • 所有的策略類都需要對外暴露

    • 上層模組必須知道有哪些策略,然後才能決定使用哪一個策略

策略模式原來就這麼簡單!

1.4JDK的策略模式應用

不知道大家還能不能想起ThreadPoolExecutor(執行緒池):執行緒池你真不來了解一下嗎?

學習ThreadPoolExecutor(執行緒池)就肯定要知道它的構造方法每個引數的意義:

  1.    /**

  2.     * Handler called when saturated or shutdown in execute.

  3.     */

  4.    private volatile RejectedExecutionHandler handler;

  5.    public ThreadPoolExecutor(int corePoolSize,

  6.                              int maximumPoolSize,

  7.                              long keepAliveTime,

  8.                              TimeUnit unit,

  9.                              BlockingQueue<Runnable> workQueue,

  10.                              ThreadFactory threadFactory,

  11.                              RejectedExecutionHandler handler) {

  12.        //....

  13.        this.handler = handler;

  14.    }

  15.    /**

  16.     * Invokes the rejected execution handler for the given command.

  17.     * Package-protected for use by ScheduledThreadPoolExecutor.

  18.     */

  19.    final void reject(Runnable command) {

  20.        handler.rejectedExecution(command, this);

  21.    }

其中我們可以找到RejectedExecutionHandler,這個引數代表的是拒絕策略(有四種具體的實現:直接丟擲異常、使用呼叫者的執行緒來處理、直接丟掉這個任務、丟掉最老的任務)

其實這就是策略模式的體現了。

最後

看完會不會覺得策略模式特別簡單呀?就一個演算法介面、多個演算法實現、一個Context來包裝一下,就完事了。

推薦閱讀和參考資料:

  • https://www.cnblogs.com/lewis0077/p/5133812.html

  • 《設計模式之禪》

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69900354/viewspace-2286250/,如需轉載,請註明出處,否則將追究法律責任。

相關文章