設計模式筆記2:工廠方法模式

定義

​ 定義一個創建對象的接口,但具體讓實現這個接口的類來決定實例化哪個類,工廠方法讓類的實例化推遲到子類中進行。

類型

創建型

適用場景

創建對象需要大量的重複代碼;

客戶端(應用層)不依賴於與產品類實例如何被創建、實現等細節;

一個類通過其子類來指定創建哪個對象。

​ 工廠方法模式中,應用層也不需要知道具體產品類的類名,只需要知道所對應的工廠就可以,具體的產品對象又具體的產品對象來創建。這個模式中利用面向對象的多態性和里氏替換原則寫的程序在運行時子類對象將覆蓋父類對象,使系統擴展性更好。

​ 同時定義也說了,把創建過程推遲到子類來實現,所以創建對象的任務就委託給多個工廠中的某一個子類,客戶端無需關心哪一個工廠子類創建的產品子類,需要的時候在動態指定(可以把工廠類的類名放在配置文件中,進行動態創建)。

優點

用戶只需要關心所需產品對應的工廠,無需關心創建細節;

加入新的產品符合開閉原則,提高可擴展性

​ 我們用工廠方法模式來創建所需要的產品,同時又隱藏了這些產品被實例化的細節,基於工廠這個角色和產品角色的多態性設計,是工廠方法模式的一個關鍵,能讓工廠自己決定創建什麼產品對象,而創建細節被封裝在工廠內部。

缺點

類的個數容易過多,增加複雜度;

增加了系統的抽象性和理解難度

​ 要根據實際需求來考慮選擇。

代碼實現工廠方法模式

代碼:https://github.com/liujinmai/studey_design_patter/tree/master/src/main/java/design/pattern/creational/factorymethod

先來看看簡單工廠中的工廠類VideoFactory:

​ 這個類中創建各個課程過程都是在這個工廠中,而工廠方法說的是把創建的智能移交到子類,現在的VideoFactory沒有對應的子類,把創建延遲到工廠的子類中是工廠方法的一個核心。

​ 現在要對VideoFactory進行改造,讓它成爲子類負責實例化對應的課程,讓VideoFactory這個superclass的代碼和子類創建對象的代碼解耦。

​ 模擬的場景和 簡單工廠 一樣,所以先把建簡單工廠的所有類copy到com.ljm.design.pattern.creational .factorymethod包下,注意引入的包路徑是否是正確的,一般情況下不會出錯,

​ 然後開始改造VideoFactory,將原來的代碼註釋或者刪除,並將VideoFactory改爲抽象類,添加getVideo的抽象方法:

//如果裏面沒有已知的實現,可以使用接口定義
public abstract class VideoFactory {
    /**
     * 獲取課程的方法
     * 改成抽象方法之後,原來的參數不在需要傳了
     * 需要在子類中進行實現
     */
    public abstract Video getVideo(/* String type */);
}

再來確定一下Video類,對於生產JavaVideo還是生產PythonVideo,這些的產品等級是一致的。

同一個產品等級:JavaVideo、PythonVideo都是屬於Video這個一個產品等級。

工廠方法是爲了解決同一產品等級的業務抽象問題。

先來創建一個java視頻的工廠JavaVideoFactory,它集成VideoFactory,注意不要導錯包:

public class JavaVideoFactory extends VideoFactory {
    //直接返回JavaVideo對象
    @Override
    public Video getVideo() {
        return new JavaVideo();
    }
}

同理,在創建PythonVideoFactory,

public class PythonVideoFactory extends VideoFactory {
    //直接返回PythonVideo對象
    @Override
    public Video getVideo() {
        return new PythonVideo();
    }
}

​ 現在具體產生什麼類型的視頻是由VideoFactory的子類來決定的,而不是交給VideoFactory來決定,VideoFactory只定義規範、契約。

​ 來到Test類進行測試,先刪除原來的代碼,

public static void main(String[] args) {
    //父類聲明的      引用      指向 子類實現
    VideoFactory videoFactory  =  new JavaVideoFactory();//創建好java的視頻工廠
    //獲得Video,用videoFactory進行賦值
    Video video = videoFactory.getVideo();
    //調用video的生產方法
    video.produce();
}

如果要生產PythonVideo,改VideoFactory的子類實現就可以了,這樣對於應用層的修改就非常簡單。

​ 從擴展性的角度來看,現在如果要添加一門前端課程,在原來簡單工廠的VideoFactory中要修改代碼才能實現。現在只需要創建FeVideo繼承Video和創建FeVideoFactory繼承VideoFactory並實現即可。

​ 現在來看一下目前的UML圖:

​ 圖中Video有三個子類:FeVideo、PythonVideo、JavaVideo,他們處於同一產品等級。VideoFactory也有三個子類。

補充:

前面說了產品等級的概念在補充一下產品族的概念:

​ 現在有視頻課程,這些視頻就是教程,教程就是視頻;但是產品經理後面提要求了:有了視頻就要有對應的“視頻疑問解答”,視頻和疑問解答打包在一起纔可以稱之爲教程。

​ 例如:對於java的視頻、java的解答資料都屬於java,他們都屬於同一產品族;而FeVideo、PythonVideo、JavaVideo屬於同一產品等級,java/Python/FE的解答資料屬於同於產品等級。

​ 在舉個例子:華爲的的手機,小米的手機都是手機,都是同一產品等級,但是小米的手機,小米的手環,小米的智能音箱等等都是小米家的,屬於同一產品族。

​ 理解產品等級和產品族有利於理解工廠方法模式和抽象工廠模式,工廠方法是爲了解決同一產品等級的業務抽象問題,抽象工廠就是爲了解決同一產品族的問題。工廠方法和抽象工廠最大的區別就在於此。

​ 然後再來看一下依賴關係:

應用層Test創建需要的對應的VideoFactory的子類工廠,然後子類工廠只負創建對應的視頻課程對象,以此類推。

名詞解釋:

上面UML圖相關的名詞:

產品:產品就是視頻Video

具體的產品:是Video的子類

創建者:是VideoFactory

實際創建者:VideoFactory的子類

一起看源碼:

接下來看看工廠方法在一些源碼的體現,還是選擇JDK1.7版本

Collection類:

找到java.util.Collection接口中的iterator()方法:

/**
 * Returns an iterator over the elements in this collection.  There are no
 * guarantees concerning the order in which the elements are returned
 * (unless this collection is an instance of some class that provides a
 * guarantee).
 *
 * @return an <tt>Iterator</tt> over the elements in this collection
 */
Iterator<E> iterator();

這裏iterator就是一個工廠方法,整個Collection是一個抽象工廠,這裏暫時不關注Collection,iterator作爲一個工程的方法,他的有很多實現類,很多人也使用過這個方法,我們先進入到iterator在ArrayList類中的實現:

public Iterator<E> iterator() {
    return new Itr();
}

ArrayList使用了一個內部類具體實現,找到這個類,就在他的下面。

/**
 * An optimized version of AbstractList.It
 */
private class Itr implements Iterator<E> {
    int cursor;       // index of next element to return
    int lastRet = -1; // index of last element returned; -1 if no such
    int expectedModCount = modCount;

    public boolean hasNext() {
        return cursor != size;
    }

    @SuppressWarnings("unchecked")
    public E next() {
        checkForComodification();
        int i = cursor;
        if (i >= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i + 1;
        return (E) elementData[lastRet = i];
    }

    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            ArrayList.this.remove(lastRet);
            cursor = lastRet;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

​ 這裏就是一個典型的工廠方法,Collection接口的方法交給子類之一ArrayList來具體實現。使用不同的工廠類中的iterator方法,能得到不同的產品實例。

​ ArrayList就是實際創建者(具體的實現工廠),實現了工廠方法;具體的抽象產品就是Iterator接口;那麼具體的實現產品就是ArrayList裏面的內部類Itr,Itr實現了Iterator接口。具體實現在Itr中,然後再具體的工廠裏(ArrayList裏的iterator)返回了具體的產品(Iterator的一個實例)。

​ 類比來看:

Collection裏面的Iterator iterator();相當於我們寫的VideoFactory裏面的getVideo; ArrayList相當於具體的實現工廠例如JavaVideoFactory;Iterator接口就是抽象產品相當於Video抽象類;ArrayList裏面的內部類Itr就是實際產品相當於例如JavaVideo。

URLStreamHandlerFactory類:

再來看看java.net.URLStreamHandlerFactory,

public interface URLStreamHandlerFactory {
    URLStreamHandler createURLStreamHandler(String protocol);
}

這個接口爲URL流協議處理程序定義了一個工廠 ,主要是解決Url協議擴展使用的,參數protocol決定了協議規範。

很明顯URLStreamHandlerFactory是一個工廠,並且只有一個方法,來看看他的具體實現Launcher,裏面有一個內部類:

private static class Factory implements URLStreamHandlerFactory {
    	//這是一個前綴
        private static String PREFIX = "sun.net.www.protocol";

        private Factory() {
        }

        public URLStreamHandler createURLStreamHandler(String var1) {
            //前綴和參數拼接形
            String var2 = PREFIX + "." + var1 + ".Handler";

            try {
                //通過Class.forName加載Class對象的方法得到一個Class對象
                //前面的簡單工廠也有相似實現
                Class var3 = Class.forName(var2);
                return (URLStreamHandler)var3.newInstance();
            } catch (ClassNotFoundException var4) {
                var4.printStackTrace();
            } catch (InstantiationException var5) {
                var5.printStackTrace();
            } catch (IllegalAccessException var6) {
                var6.printStackTrace();
            }

            throw new InternalError("could not load " + var1 + "system protocol handler");
        }
    }

可以看到在一個Factory裏面,這個Factory是一個靜態類實現了URLStreamHandlerFactory。

對於createURLStreamHandler方法的返回值URLStreamHandler,如果他是實現工廠,可以猜測一下他會不會有很多實現類?進入URLStreamHandler,看到他是一個抽象類,既然作爲返回值肯定有子類實現它,按住Ctrl+Alt+B發現有很多子類,我們先選擇熟悉的Http協議進入:

發現Handler直接繼承了URLStreamHandler,可以再看看URLStreamHandler其他實現類。

​ 在看到URLStreamHandlerFactory的具體實現Launcher裏的Factory類的createURLStreamHandler方法,這個返回值就是Handler很多具體子類。

再對應一下我們寫的代碼:

​ URLStreamHandler對應Video,抽象產品;

​ URLStreamHandler的實現類Handler都是具體產品;

​ URLStreamHandlerFactory對應VideoFactory,創建者;

​ Launcher內部類Factory對應例如JavaVideoFactory等,具體創建者;

LoggerFactory類:

再來看看工廠方法在LogerBack依賴的slf4j包中的具體應用,來到org.slf4j.LoggerFactory,找到getLogger:

public static Logger getLogger(String name) {
  ILoggerFactory iLoggerFactory = getILoggerFactory();
  return iLoggerFactory.getLogger(name);
}

這裏首先拿到的了ILoggerFactory,進入ILoggerFactory:

public interface ILoggerFactory {
  public Logger getLogger(String name);
}

只有一個方法,查看一下他的UML類圖(Shift+Ctrl+Alt+U),並把它的三個實現都顯示出來(選中ILoggerFactory後Ctrl+Alt+B),並打開他們的方法:

​ 可以看到ILoggerFactory作爲抽象工廠,有三個具體的實現類來創建對應的對象。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章