策略模式原來這麼簡單!

前言

只有光頭才能變強

回顧前面:html

不管是面試仍是我的的提高,設計模式是必學的。今天來說解策略模式~java

1、策略模式介紹

我一次聽到策略模式這個詞,是在我初學JDBC的時候。不知道你們有沒有用過DBUtils這個組件。當時初學跟着視頻學習,方立勳老師首先是讓咱們先本身封裝一下JDBC的一些經常使用的操做(實際上就是模仿DBUtils這個組件)。git

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

  • 好比說,有的時候咱們返回的是一條數據,那咱們想將這條數據封裝成一個Bean對象
  • 好比說,有的時候咱們返回的是多條數據,那咱們想將這多條數據封裝成一個List<Bean> 集合
  • 好比說,有的時候咱們返回的是xxxx數據,那咱們想將這多條數據封裝成一個Map<Bean> 集合
  • ........等等等

當時解決方案是這樣的:面試

  • 先定義一個接口:ResultSetHandler(調用者想要對結果集進行什麼操做,只要實現這個接口便可)算法

    • 這個接口定義了行爲。Object hanlder(ResultSet resultSet);
  • 而後實現上面的接口,好比咱們要封裝成一個Bean對象,就是public class BeanHandler implements ResultSetHandler
  • 調用的時候,實際上就是query()查詢方法多一個參數 query(String sql, Object[] objects, ResultSetHandler rsh)。調用者想要返回什麼類型,只要傳入相對應的ResultSetHandler實現類就是了。

代碼以下:sql

query方法:

    //這個方法的返回值是任意類型的,因此定義爲Object。
    public static Object query(String sql, Object[] objects, ResultSetHandler rsh) {

        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;

        try {
            connection = getConnection();
            preparedStatement = connection.prepareStatement(sql);

            //根據傳遞進來的參數,設置SQL佔位符的值
            if (objects != null) {
                for (int i = 0; i < objects.length; i++) {
                    preparedStatement.setObject(i + 1, objects[i]);
                }
            }


            resultSet = preparedStatement.executeQuery();

            //調用調用者傳遞進來實現類的方法,對結果集進行操做
            return rsh.hanlder(resultSet);
    }

    接口:

    /*
    * 定義對結果集操做的接口,調用者想要對結果集進行什麼操做,只要實現這個接口便可
    * */
    public interface ResultSetHandler {
         Object hanlder(ResultSet resultSet);
    
    }

    接口實現類(Example):

    //接口實現類,對結果集封裝成一個Bean對象
    public class BeanHandler implements ResultSetHandler {
    
    
        //要封裝成一個Bean對象,首先要知道Bean是什麼,這個也是調用者傳遞進來的。
        private Class clazz;
    
        public BeanHandler(Class clazz) {
            this.clazz = clazz;
        }
    
        @Override
        public Object hanlder(ResultSet resultSet) {
    
            try {
    
                //建立傳進對象的實例化
                Object bean = clazz.newInstance();
    
                if (resultSet.next()) {
    
                    //拿到結果集元數據
                    ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
    
                    for (int i = 0; i < resultSetMetaData.getColumnCount(); i++) {
    
                        //獲取到每列的列名
                        String columnName = resultSetMetaData.getColumnName(i+1);
    
                        //獲取到每列的數據
                        String columnData = resultSet.getString(i+1);
    
                        //設置Bean屬性
                        Field field = clazz.getDeclaredField(columnName);
                        field.setAccessible(true);
                        field.set(bean,columnData);
                    }
    
                    //返回Bean對象
                    return bean;
                }

這就是策略模式??就這??這不是多態的使用嗎??設計模式

1.1策略模式講解

《設計模式之禪》:ide

定義一組算法,將每一個算法都封裝起來,而且使他們之間能夠互換

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

策略模式通用類圖

策略的接口和具體的實現應該很好理解:

  • 策略的接口至關於咱們上面所講的ResultSetHandler接口(定義了策略的行爲)
  • 具體的實現至關於咱們上面所講的BeanHandler實現(接口的具體實現)

    • 具體的實現通常還會有幾個,好比可能還有ListBeanHandler、MapBeanHandler等等

使人想不明白的多是:策略模式還有一個Context上下文對象。這對象是用來幹什麼的呢?

《設計模式之禪》:

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

在知乎上也有相似的問題(爲何不直接調用,而要經過Person?):

知乎問題

說白了,經過Person來調用更符合面向對象(屏蔽了直接對具體實現的訪問)。

首先要明白一個道理,就是——究竟是 「人」 旅遊,仍是火車、汽車、自行車、飛機這些交通工具旅遊?
若是沒有上下文的話,客戶端就必須直接和具體的策略實現進行交互了,尤爲是須要提供一些公共功能或者是存儲一些狀態的時候,會大大增長客戶端使用的難度;引入上下文以後,這部分工做能夠由上下文來完成,客戶端只須要和上下文進行交互就能夠了。這樣可讓策略模式更具備總體性,客戶端也更加的簡單

具體的連接:

因此咱們再說回上文的通用類圖,咱們就能夠這樣看了:

DBUtils策略模式類圖

1.2策略模式例子

如今3y擁有一個公衆號,名稱叫作Java3y。3y想要這讓更多的人認識到Java3y這個公衆號。因此天天都在想怎麼漲粉(hahah

因而3y就開始想辦法了(操碎了心),同時3y在這一段時間下來發現漲粉的方式有不少。爲了方便,定義一個通用的接口方便來管理和使用唄。

接口:

/**
 * 增長粉絲策略的接口(Strategy)
 */
interface IncreaseFansStrategy {

    void action();
}

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

/**
 * 請水軍(ConcreteStrategy)
 */
public class WaterArmy implements IncreaseFansStrategy {
    
    @Override
    public void action() {
        System.out.println("3y牛逼,我要給你點贊、轉發、加雞腿!");
    }
}

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

/**
 * 認真寫原創(ConcreteStrategy)
 */
public class OriginalArticle implements IncreaseFansStrategy{

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

    }
}

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

說到底,不管是哪一種漲粉方法,都是經過3y去執行的。

/**
 * 3y(Context)
 */
public class Java3y {

    private IncreaseFansStrategy strategy ;

    public Java3y(IncreaseFansStrategy strategy) {
        this.strategy = strategy;
    }

    // 3y要發文章了(買水軍了、送書了、寫知乎引流了...)。
    // 具體執行哪一個,看3y選哪一個
    public void exec() {
        strategy.action();
    }
}

因此啊,每當到了發推文的時候,3y就能夠挑用哪一種方式漲粉了:

public class Main {

    public static void main(String[] args) {

        // 今天2018年12月24日
        Java3y java3y = new Java3y(new WaterArmy());
        java3y.exec();

        // 明天2018年12月25日
        Java3y java4y = new Java3y(new OriginalArticle());
        java4y.exec();
        
        // ......
    }
}

執行結果:

執行結果

1.3策略模式優缺點

優勢:

  • 算法能夠自由切換

    • 改一下策略很方便
  • 擴展性良好

    • 增長一個策略,就多增長一個類就行了。

缺點:

  • 策略類的數量增多

    • 每個策略都是一個類,複用的可能性很小、類數量增多
  • 全部的策略類都須要對外暴露

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

調用方必需要知道有哪些策略

1.4JDK的策略模式應用

不知道你們還能不能想起ThreadPoolExecutor(線程池):線程池你真不來了解一下嗎?

學習ThreadPoolExecutor(線程池)就確定要知道它的構造方法每一個參數的意義:

/**
     * Handler called when saturated or shutdown in execute.
     */
    private volatile RejectedExecutionHandler handler;

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        //....
        this.handler = handler;
    }

    /**
     * Invokes the rejected execution handler for the given command.
     * Package-protected for use by ScheduledThreadPoolExecutor.
     */
    final void reject(Runnable command) {
        handler.rejectedExecution(command, this);
    }

其中咱們能夠找到RejectedExecutionHandler,這個參數表明的是拒絕策略(有四種具體的實現:直接拋出異常、使用調用者的線程來處理、直接丟掉這個任務、丟掉最老的任務)

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

最後

看完會不會以爲策略模式特別簡單呀?就一個算法接口、多個算法實現、一個Context來包裝一下,就完事了。

推薦閱讀和參考資料:

樂於分享和輸出 乾貨的Java技術公衆號:Java3y。

帥的人都關注了

文章的目錄導航

相關文章
相關標籤/搜索