設計模式 - 工廠模式

概述

咱們都知道Java中共有 23 種設計模式,其中工廠模式分爲三種,即:簡單工廠模式(不在 23 種設計模式之列)、工廠方法模式和抽象工廠模式;咱們平時說的工廠模式,其實大都指工廠方法模式,這種模式是咱們平時編碼中用的頻率最高的一種,在Spring源碼中就有不少工廠模式的應用,好比 BeanFactoryjava

下面依次按照簡單工廠模式、工廠方法模式、抽象工廠模式的順序,依次由淺入深說說這三種模式;文章分別從定義、場景、優缺點也示例進行講解。git

簡單工廠模式

定義

簡單工廠模式(Simple Factory Pattern)是指由一個工廠對象決定建立出哪種產品類的實例,簡單來講就是,定義一個工廠類,根據傳入的參數不一樣返回不一樣的實例,被建立的實例具備共同的父類或接口。github

場景

簡單工廠適用於工廠類負責建立的對象較少的場景,且客戶端只須要傳入工廠類的參數,對於如何建立對象的邏輯不須要關心。總結一下就是:設計模式

  1. 須要建立的對象較少;
  2. 客戶端不關心對象的建立過程;

優缺點

優勢

實現了對責任的分割,提供了專門的工廠類用於建立對象ide

缺點

工廠類的職責相對太重,不易於擴展過於複雜的產品結構,不符合開閉原則(可解決)測試

示例

接下來咱們構造一個場景來看看簡單工廠模式的應用:如今手機更新換代的比較快,手機廠商每一年基本都會在不一樣時間或者在同一時間發佈生產不一樣型號和配置的手機。優化

假設某手機公司最近發佈了型號爲 A、B 的手機,其中生產任務交給代工廠去生產;咱們都知道無論什麼類型的手機都屬於手機,因此咱們先建立一個手機類Phone,並在其中聲明一個公共的手機型號方法type編碼

/**
 * @author eamon.zhang
 * @date 2019-09-27 上午10:55
 */
public interface Phone {
    void type();
}複製代碼

而後定義具體的手機類型:spa

型號 A:設計

/**
 * @author eamon.zhang
 * @date 2019-09-27 上午11:02
 */
public class PhoneA implements Phone {
    @Override
    public void type() {
        System.out.println("型號爲A的手機!");
    }
}複製代碼

型號 B:

/**
 * @author eamon.zhang
 * @date 2019-09-27 上午11:03
 */
public class PhoneB implements Phone {
    @Override
    public void type() {
        System.out.println("型號爲B的手機!");
    }
}複製代碼

建立手機代工廠 PhoneFactory 類:

/**
 * @author eamon.zhang
 * @date 2019-09-27 上午10:54
 */
public class PhoneFactory {
    public Phone product(String type) {
        switch (type) {
            case "A":
                return new PhoneA();
            case "B":
                return new PhoneB();
            default:
                return null;
        }
    }
}複製代碼

測試:

/**
 * @author eamon.zhang
 * @date 2019-09-27 上午11:09
 */
public class PhoneFactoryTest {

    @Test
    public void product() {
        PhoneFactory phoneFactory = new PhoneFactory();
        phoneFactory.product("A").type();

        phoneFactory.product("B").type();
    }
}複製代碼

輸出:

型號爲A的手機!
型號爲B的手機!複製代碼

固然,爲了方便調用,PhoneFactory 中的product()也能夠寫成靜態的。

類圖:

拓展

解決不符合開閉原則問題

上面的示例中,客戶端調用是簡單了,但若是咱們業務繼續擴展,增長一個型號 C,那麼上面的工廠方法中的product() 方法就得再次修改邏輯。不符合開閉原則;所以咱們客戶考慮對其進行進一步優化,利用反射技術修改product()方法:

public Phone product(String className) {
    try {
        if (!(null == className || "".equals(className))) {
            return (Phone) Class.forName(className).newInstance();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}複製代碼

修改客戶端調用代碼:

public void product() {
    PhoneFactory phoneFactory = new PhoneFactory();
    phoneFactory.product("com.eamon.javadesignpatterns.factory.PhoneA").type();

    phoneFactory.product("com.eamon.javadesignpatterns.factory.PhoneB").type();
}複製代碼

通過優化以後,從此再增長型號,就不用去修改工廠方法了;可是又有一個問題,方法參數是很長的字符串,可控性有待提高,並且還須要強制轉型,不方便閱讀和維護,因此進一步改造:

public Phone product(Class<? extends Phone> clazz) {
    try {
        if (null != clazz) {
            return clazz.newInstance();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}複製代碼

優化客戶端調用代碼:

@Test
public void product() {
    PhoneFactory phoneFactory = new PhoneFactory();
    phoneFactory.product(PhoneA.class).type();

    phoneFactory.product(PhoneB.class).type();
}複製代碼

再來看一下類圖:

其餘

簡單工廠模式在 JDK 源碼中也無處不足,好比經常使用的 Calendar類中Calendar.getInstance()方法,跟進源碼到createCalendar(TimeZone zone,Locale aLocale)就能夠看出。

還有就是 經常使用的logback,咱們能夠看到 LoggerFactory 中有多個重載的方法 getLogger():

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

public final Logger getLogger(final Class<?> clazz) {
    return getLogger(clazz.getName());
}複製代碼

工廠方法模式

定義

工廠方法模式(Fatory Method Pattern)是指定義一個建立對象的接口,但讓實現這個 接口的類來決定實例化哪一個類,工廠方法讓類的實例化推遲到子類中進行。

在工廠方法模式中用戶只須要關心所需產品對應的工廠,無須關心建立細節,並且加入新的產品符 合開閉原則。

工廠方法模式主要解決產品擴展的問題,在簡單工廠中,隨着產品鏈的豐富,若是每一個手機的建立邏輯有區別的話,工廠的職責會變得愈來愈多,有點像萬能工廠,並不便於維護。根據單一職責原則咱們將職能繼續拆分,專人幹專事。

場景

工廠方法適用於如下場景:

  1. 建立對象須要大量重複的代碼。
  2. 客戶端(應用層)不依賴於產品類實例如何被建立、實現等細節。
  3. 一個類經過其子類來指定建立哪一個對象。

優缺點

優勢

  1. 具備良好的封裝性,代碼結構清晰,井底了模塊間的耦合。
  2. 拓展性很是優秀。(在增長產品類的狀況下,只要修改具體的工廠類或擴展一個工廠類)
  3. 屏蔽了產品類。(產品類的實現如何變化,調用者不須要關心)

缺點:

一、類的個數容易過多,增長複雜度。二、增長了系統的抽象性和理解難度。

示例

A 型號手機由PhoneA工廠建立,B 型號手機由PhoneB工廠建立,對工廠自己也作一個抽象。來看代碼,先建立 PhoneFactory 接口:

/**
 * @author eamon.zhang
 * @date 2019-09-27 下午1:45
 */
public interface PhoneFactory {
   Phone product();
}複製代碼

分別建立子工廠 PhoneAFactory

/**
 * @author eamon.zhang
 * @date 2019-09-27 下午1:50
 */
public class PhoneAFactory implements PhoneFactory {
    @Override
    public Phone product() {
        return new PhoneA();
    }
}複製代碼

PhoneBFactory 類:

/**
 * @author eamon.zhang
 * @date 2019-09-27 下午1:50
 */
public class PhoneBFactory implements PhoneFactory {
    @Override
    public Phone product() {
        return new PhoneB();
    }
}複製代碼

看測試代碼:

/**
 * @author eamon.zhang
 * @date 2019-09-27 下午1:54
 */
public class PhoneFactoryTest {

    @Test
    public void product() {
        PhoneFactory factory = new PhoneAFactory();
        factory.product().type();

        factory = new PhoneBFactory();
        factory.product().type();

    }
}複製代碼

測試結果:

型號爲A的手機!
型號爲B的手機!複製代碼

再看一下類圖:

拓展

再來看看 logback 中工廠方法模式的應用,看看類圖就 OK 了:

抽象工廠模式

定義

抽象工廠模式(Abastract Factory Pattern)是指提供一個建立一系列相關或相互依賴對象的接口,無需指定他們具體的類。

客戶端(應用層)不依賴於產品類實例如何被建立、實現等細節。強調的是一系列相關的產品對象(屬於同一產品族)一塊兒使用建立對象須要大量重複的代碼。須要提供一個產品類的庫,全部的產品以一樣的接口出現,從而使客戶端不依賴於具體實現。

理解

爲了便於你們理解抽象工廠,咱們先了解兩個概念產品等級結構和產品族,看下面的圖:從上圖中看出有正方形,圓形和三角形三種圖形,相同顏色深淺的就表明同一個產品族,相同形狀的表明同一個產品等級結構。一樣能夠從生活中來舉例,好比,美的電器生產多種家用電器。那麼上圖中,顏色最深的正方形就表明美的洗衣機、顏色最深的圓形表明美的空調、顏色最深的三角形表明美的熱水器,顏色最深的一排都屬於美的品牌,都是美的電器這個產品族。再看最右側的三角形,顏色最深的咱們指定了表明美的熱水器,那麼第二排顏色稍微淺一點的三角形,表明海信的熱水器。同理,同一產品結構下還有格力熱水器,格力空調,格力洗衣機。

再看下面這張圖,最左側的箭頭表明具體的工廠,有美的工廠、海信工廠、格力工廠。每一個品牌的工廠都生產洗衣機、熱水器、空調。

經過上面兩張圖的對比理解,相信你們對抽象工廠有了很是形象的理解。

場景

一個對象族(或是一組沒有任何關係的對象)都有相同的約束,則可使用抽象工廠模式。簡單來講:

  1. 和工廠方法同樣客戶端不須要知道它所建立的對象的類。
  2. 須要一組對象共同完成某種功能時。而且可能存在多組對象完成不一樣功能的狀況。
  3. 系統結構穩定,不會頻繁的增長對象。(由於一旦增長就須要修改原有代碼,不符合開閉原則)

優缺點

優勢

  • 封裝性,每一個產品的實現類不是高層模塊要關心的,它要關心的是接口,不關心對象是如何建立的,只要知道工廠類是誰,就能建立出一個須要的對象,省時省力。
  • 產品族內的約束爲非公開狀態。

缺點

  • 規定了全部可能被建立的產品集合,產品族中擴展新的產品困難,須要修改抽象工廠的接口
  • 增長了系統的抽象性和理解難度

示例

好比如今有一個應用,假如是某視頻軟件,須要在三個不一樣的平臺(Windows、IOS、Android)上運行,該應用針對每套系統都設計了一套上傳控制器(UploadController)、播放控制(DisplayController),下面經過抽象工廠模式來設計該軟件。

視頻軟件裏邊的各個平臺的UploadControllerDisplayController應該是咱們最終生產的具體產品。因此新建兩個抽象產品接口。

UploadController 接口:

/**
 * @author eamon.zhang
 * @date 2019-09-27 下午2:59
 */
public interface UploadController {
    void upload();
}複製代碼

DisplayController 接口:

/**
 * @author eamon.zhang
 * @date 2019-09-27 下午2:59
 */
public interface DisplayController {
    void display();
}複製代碼

定義抽象工廠VideoPlayerFactory類,它可以建立UploadControllerDisplayController

/**
 * 抽象工廠是主入口,在Spring中應用的最普遍的一種設計模式,易於擴展
 *
 * @author eamon.zhang
 * @date 2019-09-27 下午3:04
 */
public interface VideoPlayerFactory {
    DisplayController createDisplayController();

    UploadController createUploadController();
}複製代碼

而後在各個平臺建立具體的 UploadControllerDisplayController

建立適用於WindowsUploadControllerDisplayController

/**
 * @author eamon.zhang
 * @date 2019-09-27 下午3:09
 */
public class WindowsUploadController implements UploadController {
    @Override
    public void upload() {
        System.out.println("Windows 上傳控制器!");
    }
}

/**
 * @author eamon.zhang
 * @date 2019-09-27 下午3:09
 */
public class WindowsDisplayController implements DisplayController {

    @Override
    public void display() {
        System.out.println("Windows 上的播放器!");
    }
}複製代碼

建立適用於IOSUploadControllerDisplayController

/**
 * @author eamon.zhang
 * @date 2019-09-27 下午3:10
 */
public class IosUploaderController implements UploadController {
    @Override
    public void upload() {
        System.out.println("IOS 上傳控制器!");
    }
}

/**
 * @author eamon.zhang
 * @date 2019-09-27 下午3:09
 */
public class IosDisplayController implements DisplayController {

    @Override
    public void display() {
        System.out.println("IOS 上的播放器!");
    }
}複製代碼

建立適用於AndroidUploadControllerDisplayController

/**
 * @author eamon.zhang
 * @date 2019-09-27 下午3:10
 */
public class AndroidUploaderController implements UploadController {
    @Override
    public void upload() {
        System.out.println("Android 上傳控制器!");
    }
}

/**
 * @author eamon.zhang
 * @date 2019-09-27 下午3:09
 */
public class AndroidDisplayController implements DisplayController {

    @Override
    public void display() {
        System.out.println("Android 上的播放器!");
    }
}複製代碼

在各平臺具體的工廠類中完成上傳控制器和播放控制器的建立過程:

建立WindowsFactory類:

/**
 * @author eamon.zhang
 * @date 2019-09-27 下午3:15
 */
public class WindowsFactory implements VideoPlayerFactory {
    @Override
    public DisplayController createDisplayController() {
        return new WindowsDisplayController();
    }

    @Override
    public UploadController createUploadController() {
        return new WindowsUploadController();
    }
}複製代碼

建立IosFactory類:

/**
 * @author eamon.zhang
 * @date 2019-09-27 下午3:17
 */
public class IosFactory implements VideoPlayerFactory {
    @Override
    public DisplayController createDisplayController() {
        return new IosDisplayController();
    }

    @Override
    public UploadController createUploadController() {
        return new IosUploaderController();
    }
}複製代碼

建立AndroidFactory類:

/**
 * @author eamon.zhang
 * @date 2019-09-27 下午3:18
 */
public class AndroidFactory implements VideoPlayerFactory {
    @Override
    public DisplayController createDisplayController() {
        return new AndroidDisplayController();
    }

    @Override
    public UploadController createUploadController() {
        return new AndroidUploaderController();
    }
}複製代碼

來看客戶端調用:

/**
 * @author eamon.zhang
 * @date 2019-09-27 下午3:20
 */
public class VideoPlayerFactoryTest {

    @Test
    public void VideoPlayer() {
        VideoPlayerFactory factory = new WindowsFactory();

        // IOS
//        factory = new IosFactory();
//        // Android
//        factory = new AndroidFactory();

        UploadController uploadController = factory.createUploadController();
        DisplayController displayController = factory.createDisplayController();

        uploadController.upload();
        displayController.display();

    }
}複製代碼

以調用 Windows 爲例,結果:

Windows 上傳控制器!
Windows 上的播放器!複製代碼

上面就是針對不一樣平臺只經過建立對應的工廠對象就完成了上傳控制器和播放控制器的建立。抽象工廠很是完美清晰地描述這樣一層複雜的關係。可是,不知道你們有沒有發現,若是咱們再繼續擴展功能,將下載器也加入到產品中,那麼咱們的代碼從抽象工廠,到具體工廠要所有調整,很顯然不符合開閉原則。所以就有了上面優缺點中所說的缺點。

總結

在實際應用中,咱們千萬不能犯強迫症甚至有潔癖。在實際需求中產品等級結構升級是很是正常的一件事情。咱們能夠根據實際狀況,只要不是頻繁升級,能夠不遵循開閉原則。代碼每半年升級一次或者每一年升級一次又有何不可呢?

源碼:github.com

相關文章
相關標籤/搜索