設計模式 | 工廠方法模式及典型應用

工廠方法模式

工廠方法模式(Factory Method Pattern):定義一個用於建立對象的接口,讓子類決定將哪個類實例化。工廠方法模式讓一個類的實例化延遲到其子類。java

工廠方法模式又簡稱爲工廠模式(Factory Pattern),又可稱做虛擬構造器模式(Virtual Constructor Pattern)或多態工廠模式(Polymorphic Factory Pattern)。python

工廠方法模式是一種類建立型模式。數據庫

角色

在工廠方法模式結構圖中包含以下幾個角色:設計模式

Product(抽象產品):它是定義產品的接口,是工廠方法模式所建立對象的超類型,也就是產品對象的公共父類服務器

ConcreteProduct(具體產品):它實現了抽象產品接口,某種類型的具體產品由專門的具體工廠建立,具體工廠和具體產品之間一一對應。微信

Factory(抽象工廠):在抽象工廠類中,聲明瞭工廠方法(Factory Method),用於返回一個產品。抽象工廠是工廠方法模式的核心,全部建立對象的工廠類都必須實現該接口。網絡

ConcreteFactory(具體工廠):它是抽象工廠類的子類,實現了抽象工廠中定義的工廠方法,並可由客戶端調用,返回一個具體產品類的實例。數據結構

與簡單工廠模式相比,工廠方法模式最重要的區別是引入了抽象工廠角色,抽象工廠能夠是接口,也能夠是抽象類或者具體類app

示例

抽象產品類 Video框架

public abstract class Video {
    public abstract void produce();
}

具體產品類 JavaVideo 和 PythonVideo,須要繼承抽象產品類 Video

public class JavaVideo extends Video {
    @Override
    public void produce() {
        System.out.println("錄製Java課程視頻");
    }
}

public class PythonVideo extends Video {
    @Override
    public void produce() {
        System.out.println("錄製Python課程視頻");
    }
}

抽象工廠類 VideoFactory

public abstract class VideoFactory {
    public abstract Video getVideo();
}

具體工廠類 JavaVideoFactory 和 PythonVideoFactory,須要繼承抽象工廠類 VideoFactory

public class JavaVideoFactory extends VideoFactory {
    @Override
    public Video getVideo() {
        return new JavaVideo();
    }
}

public class PythonVideoFactory extends VideoFactory {
    @Override
    public Video getVideo() {
        return new PythonVideo();
    }
}

客戶端類,須要什麼產品則經過該產品對應的工廠類來獲取,不須要知道具體的建立過程

public class Test {
    public static void main(String[] args) {
        VideoFactory pythonVideoFactory = new PythonVideoFactory();
        VideoFactory javaVideoFactory = new JavaVideoFactory();
        Video pythonVideo = pythonVideoFactory.getVideo();
        pythonVideo.produce();
        Video javaVideo = javaVideoFactory.getVideo();
        javaVideo.produce();
    }
}

輸出

錄製Python課程視頻
錄製Java課程視頻

當須要增長一個產品 FEVideo 時,只須要增長 FEVideo 具體產品類和 FEVideoFactory 具體工廠類便可,不須要修改原有的產品類和工廠類

public class FEVideo extends Video{
    @Override
    public void produce() {
        System.out.println("錄製FE課程視頻");
    }
}

public class FEVideoFactory extends VideoFactory{
    @Override
    public Video getVideo() {
        return new FEVideo();
    }
}

修改客戶端代碼

public class Test {
    public static void main(String[] args) {
        VideoFactory feVideoFactory = new FEVideoFactory();
        Video feVideo = feVideoFactory.getVideo();
        feVideo.produce();
    }
}

還能夠經過反射機制和配置文件配合,連客戶端代碼都不須要修改

public class Test {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        // 從文件或數據庫等外部渠道獲取 工廠類名
        String factoryName = "com.designpattern.factorymethod.JavaVideoFactory";
        // 經過反射機制獲取工廠類
        Class c = Class.forName(factoryName);
        VideoFactory factory = (VideoFactory)c.newInstance();
        // 生產產品
        Video video = factory.getVideo();
        video.produce();
    }
}

最終的類圖以下所示

示例.工廠方法結構圖

工廠方法模式總結

工廠方法模式是簡單工廠模式的延伸,它繼承了簡單工廠模式的優勢,同時還彌補了簡單工廠模式的不足。工廠方法模式是使用頻率最高的設計模式之一,是不少開源框架和API類庫的核心模式。

工廠方法模式的主要優勢

  • 在工廠方法模式中,工廠方法用來建立客戶所須要的產品,同時還向客戶隱藏了哪一種具體產品類將被實例化這一細節,用戶只須要關心所需產品對應的工廠,無須關心建立細節,甚至無須知道具體產品類的類名。

  • 基於工廠角色和產品角色的多態性設計是工廠方法模式的關鍵。它可以讓工廠能夠自主肯定建立何種產品對象,而如何建立這個對象的細節則徹底封裝在具體工廠內部。工廠方法模式之因此又被稱爲多態工廠模式,就正是由於全部的具體工廠類都具備同一抽象父類。

  • 使用工廠方法模式的另外一個優勢是在系統中加入新產品時,無須修改抽象工廠和抽象產品提供的接口,無須修改客戶端,也無須修改其餘的具體工廠和具體產品,而只要添加一個具體工廠和具體產品就能夠了,這樣,系統的可擴展性也就變得很是好,徹底符合"開閉原則"。

工廠方法模式的主要缺點

  • 在添加新產品時,須要編寫新的具體產品類,並且還要提供與之對應的具體工廠類,系統中類的個數將成對增長,在必定程度上增長了系統的複雜度,有更多的類須要編譯和運行,會給系統帶來一些額外的開銷。

  • 因爲考慮到系統的可擴展性,須要引入抽象層,在客戶端代碼中均使用抽象層進行定義,增長了系統的抽象性和理解難度,且在實現時可能須要用到DOM、反射等技術,增長了系統的實現難度。

適用場景

  • 客戶端不知道它所須要的對象的類。在工廠方法模式中,客戶端不須要知道具體產品類的類名,只須要知道所對應的工廠便可,具體的產品對象由具體工廠類建立,可將具體工廠類的類名存儲在配置文件或數據庫中。

  • 抽象工廠類經過其子類來指定建立哪一個對象。在工廠方法模式中,對於抽象工廠類只須要提供一個建立產品的接口,而由其子類來肯定具體要建立的對象,利用面向對象的多態性和里氏代換原則,在程序運行時,子類對象將覆蓋父類對象,從而使得系統更容易擴展。

工廠方法模式的典型應用及源碼分析

Java集合接口 Collection 中的工廠方法模式

Collection 中的 iterator 方法以下:

public interface Collection<Eextends Iterable<E{
    Iterator<E> iterator();
    // ...省略
}

關於 iterator 方法的介紹:  
Java的迭代器只在Collection中有,而Map沒有迭代器,它有不一樣的迭代方法;  
迭代器的終極目標:就是用統一的方法來迭代不一樣類型的集合!可能因爲不一樣集合的內部數據結構不盡相同,若是要本身純手工迭代的話相互之間會有很大的差異,而迭代器的做用就是統一的方法對不一樣的集合進行迭代,而在迭代器底層隱藏不一樣集合之間的差別,從而爲迭代提供最大的方便  
使用用迭代器迭代的步驟: i. 第一步確定是先獲取集合的迭代器:調用集合的iterator方法就能得到,Iterator Collection.iterator(); ii. 使用迭代器的hasNext、next往下迭代  
Iterator的經常使用方法:boolean hasNext():是否還有下一個元素; Object next():取出下一個元素並返回; void remove(); :從容器中刪除當前元素,直接會改變容器中的數據

查看該接口的實現類,能夠看到是很是的多

Collection接口的實現類(部分)

咱們僅看其中一個實現類 java.util.ArrayList,看其對 iterator 方法的實現

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

/**
 * An optimized version of AbstractList.Itr
 */

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;

    Itr() {}

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

    @SuppressWarnings("unchecked")
    public E next() {
        // ...省略...
    }

    public void remove() {
        // ...省略...
    }

    @Override
    @SuppressWarnings("unchecked")
    public void forEachRemaining(Consumer<? super E> consumer) {
        // ...省略...
    }

    final void checkForComodification() {
        // ...省略...
    }
}

Itr 類實現了 iterator 接口,iterator 接口正是 Collection 接口中 iterator 方法的返回類型,其代碼以下:

public interface Iterator<E{
    boolean hasNext();

    next();

    default void remove() {
        throw new UnsupportedOperationException("remove");
    }

    default void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }
}

因而可知Collection 接口扮演了抽象工廠角色,工廠方法爲 iterator()Collection 的實現類譬如 ArrayList 扮演了具體工廠角色,而抽象產品爲 Iterator 接口,具體產品爲 Itr

java.net 網絡包中的工廠方法模式

URLStreamHandlerFactory 接口爲 URL 流協議處理程序定義一個工廠。URL 類使用它可爲特定的協議建立 URLStreamHandler

public interface URLStreamHandlerFactory {
    /**
     * Creates a new {@code URLStreamHandler} instance with the specified protocol.
     *
     * @param   protocol   the protocol ("{@code ftp}", "{@code http}", "{@code nntp}", etc.).
     * @return  a {@code URLStreamHandler} for the specific protocol.
     * @see     java.net.URLStreamHandler
     */

    URLStreamHandler createURLStreamHandler(String protocol);
}

該接口的實現類爲 sun.misc.Launcher 中的內部類 Factory

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 var3 = Class.forName(var2);
            return (URLStreamHandler)var3.newInstance();
        } catch (ReflectiveOperationException var4) {
            throw new InternalError("could not load " + var1 + "system protocol handler", var4);
        }
    }
}

能夠看到 createURLStreamHandler 方法的實現爲:傳入參數,拼接前綴和後綴,以後經過反射機制獲取建立一個 URLStreamHandler 對象

URLStreamHandler 是一個抽象類,其中的方法以下圖,只有 openConnection 爲抽象方法,其餘方法均有具體實現

URLStreamHandler抽象類中的方法

關於URLStreamHandler:    
抽象類URLStreamHandler是全部流協議處理程序的通用超類。 流協議處理程序知道如何爲特定協議類型創建鏈接,例如http或https

其子類有以下(19個):

URLStreamHandler的子類

查看其中一個子類譬如 sun.net.www.protocol.http.Handler

public class Handler extends URLStreamHandler {
    protected String proxy;
    protected int proxyPort;

    protected int getDefaultPort() {
        return 80;
    }

    public Handler() {
        this.proxy = null;
        this.proxyPort = -1;
    }

    public Handler(String var1, int var2) {
        this.proxy = var1;
        this.proxyPort = var2;
    }

    protected URLConnection openConnection(URL var1) throws IOException {
        return this.openConnection(var1, (Proxy)null);
    }

    protected URLConnection openConnection(URL var1, Proxy var2) throws IOException {
        return new HttpURLConnection(var1, var2, this);
    }
}

該類實現的 openConnection 方法的返回值類型爲 URLConnection,最終返回了一個 HttpURLConnection 對象

咱們又繼續看 java.net.URLConnection,這也是一個抽象類

image

URLConnection介紹

  • URLConnection是一個功能強大的抽象類,它表示指向URL指定資源的活動鏈接。
    與URL類相比,它與服務器的交互提供了更多的控制機制。尤爲服務器是HTTP服務器,可使用URLConnection對HTTP首部的訪問,能夠配置發送給服務器的請求參數。固然也能夠經過它讀取服務器的數據以及向服務器寫入數據.

  • URLConnection是Java的協議處理器機制的一部分。協議處理器機制是將處理協議的細節與特定數據類型分開。若是要實現一個特定的協議,則實現URLConnection的子類便可。程序運行時能夠將該子類做爲一個具體的協議處理器來使用。  

  • 使用URLConnection類的步驟:1.  構造一個URL對象;2. 調用該URL的openConnection()獲取一個URLConnection;3. 配置這個URLConnection;4. 讀取首部字段;5. 得到輸入流並讀取數據;6. 得到輸出流並寫入數據;7. 關閉鏈接

其子類有23個

image

咱們能夠畫出他們的關係圖以下所示

URLConnection關係圖

由此可知:抽象工廠角色爲 URLStreamHandlerFactory,工廠方法爲 createURLStreamHandler,抽象產品角色爲 URLStreamHandler,具體產品角色爲 URLStreamHandler 的子類譬如 sun.net.www.protocol.http.Handlersun.net.www.protocol.ftp.Handler

同時URLStreamHandler 也扮演了抽象工廠角色,工廠方法爲 openConnectionURLStreamHandler 的子類譬如 sun.net.www.protocol.http.Handler 也扮演了具體工廠角色,抽象產品爲 URLConnection,具體產品角色爲  URLConnection 的子類如 sun.net.www.protocol.http.HttpURLConnection

Logback 中的工廠方法模式

在上一篇文章《設計模式 | 簡單工廠模式及典型應用》 介紹的 Logback 裏有簡單工廠模式,其實也有工廠方法模式,畫圖以下

iLoggerFactory類關係

能夠看出,抽象工廠角色爲 ILoggerFactory 接口,工廠方法爲 getLogger,具體工廠角色爲 LoggerContextNOPLoggerFactorySubstituteLoggerFactory 等,抽象產品角色爲 Logger,具體產品角色爲 Logger 的實現類以下

Logger 的實現類

而簡單工廠模式應用在 LoggerContext 的  getLogger 方法中,根據參數返回相應的 Logger 對象

後記

點擊[閱讀原文]可訪問個人我的博客:http://laijianfeng.org

關注【小旋鋒】微信公衆號,及時接收博文推送

參考:  
劉偉:設計模式Java版  
慕課網java設計模式精講 Debug 方式+內存分析


本文分享自微信公衆號 - 小旋鋒(whirlysBigData)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索