--- title: 'Abstract Factory 抽象工廠 模式' disqus: kyleAlien --- Abstract Factory 抽象工廠 模式 === ## Overview of Content 如有引用參考請詳註出處,感謝 :smile: > [**Factory 工廠方法**](https://hackmd.io/40ZKAzDHTF6PMpbez-3vNg?view) 的升級版,抽象工廠多了依賴介面,**多型拓展並依賴其抽象** :::success * 如果喜歡讀更好看一點的網頁版本,可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/) 本篇文章對應的是 [**Abstract Factory 設計模式 | 實現解說 | Android MediaPlayer**](https://devtechascendancy.com/object-oriented_design_abstract-factory/) ::: [TOC] ## Abstract Factory 使用場景 一個 **物件集合有 ++相同約束(功能)++,但實作方法不同時**,即可使用 > 就像是 Window 系統、Mac 系統 都有藍芽功能(Factory),但掃描、連接、傳輸...實做都不同(Product),這就可以使用抽象工廠模式 ### Abstract Factory 定義 & UML * **`Abstract Factory` 定義**: **為創建一組 相關 或 相互依賴 的對象 提供一個抽象接口**,而 **不指定它們的具體類** :::success * **工廠模式 vs 抽象工廠模式** * **相同點** 工廠模式、抽象工廠的建構對象都在內部,**使用者不用關心實際對象,而是關注界面(契約)** * **不同點**:**兩者對於生產的目的有明顯的不同** 工廠模式中,使用者會關注 `Factory` 產生的 `Product`,產品與工廠的關係不大 > 工廠模式生產 **一個產品**,這個產品有契約上定義的行為操作,但產品有不同特色(實作) **抽象工廠則是 `Product` 與 `Factory` 有一定的關聯性 (關係更重),使用不同的 `Product` 會造出不同產品** > 抽象工廠生產多個產品(++**一系列產品**++),一組契約等著被實現,而各個產品也可以有不同特點 * 抽象工廠手法的不同點 抽象工廠將工廠主體類別中的抽象產品「提煉為一個產品界面」,用物件合成(Object Composition) 取代了類別繼承,所以會更加的靈活,也更加的抽象、複雜 ::: * 抽象工廠 UML:抽象工廠的 **重點是在抽象 Factory 上依賴其他抽象 Product**,透過操控工廠產生不同產品 | 類 | 功能 | | -------- | -------- | | `IFactory` (抽象) | 依賴抽象 Product、所以產生的產品也是抽象 | | `ConcreateFactory_A、B` | 實做工廠類,藉由抽象產品,創建目標類 | | `IProductA、B` (抽象) | 抽象產品,也就是各個產品的行為契約 | | `ConcreateProdcut_A1`、`A2`、`B1`、`B2` | 定義抽象產品的細節 | > ![](https://i.imgur.com/gDTy7nJ.png) :::info * 一般來講有多少 Product 就對應有多少 Factory ::: ### Abstract Factory 設計 - 優缺點 * Abstract Factory 設計**優點** * 具體類別利用 **抽像解偶,基於介面與實作的分離**,由繼承者實作方法 * 可以使用反射方式可以減少實作工廠類 :::warning * 但使用反射技巧後,相對的使用者必須知道細節,**這不符合 `最少知識原則`**,在使用時要自己做些取捨 ::: * Abstract Factory 設計**缺點** * **Product 新增的功能,代表所有類都要新增這項功能,不符合開閉原則 (這也是里式原則的缺點)**;可以使用 `介面隔離` 原則,讓介面立度最小化 :::success * 上面所說的是直向拓展(繼承)的 `Product` 會影響到各個子類別;但橫向拓展的 `Factory` 就不會受到這個影響 ::: ## Abstract Factory 實做 以下我們實現一組通訊協議 * 這組通訊協議作為產品(`Product`)需要提供創建通訊物件(`create`)、取得起始碼(`getStartCode`)這兩個功能 * 而實作的工廠(`Factory`)需要提供使用者 `SPI`、`UART`、`MIX` 通訊協議 ### Abstract Factory 標準 1. **`IProduct A`、`B` 類** :首先決定抽象產品,這邊我們要定義一組通訊協定,這個協定有 Create、Start code ... 等等規範,但是實際要由通訊方案決定 :::info * 說明產品的共同性,而這個共通性我們就可以使用抽象來表達 ::: ```java= // 抽象產品 public interface IComm { // 產品 1 interface ICreate { void create(); } // 產品 2 interface IStart { String getStartCode(); } } ``` 2. **`ConcreateProdcutA1`、`A2`、`B1`、`B2` 類**:定義產品的實做 ```java= // 定義產品的實做 public class CreateSPI implements IComm.ICreate { @Override public void create() { System.out.println("Start create Spi comm"); } } // --------------------------------------------------------------- public class CreateUART implements IComm.ICreate { @Override public void create() { System.out.println("Start create UART comm"); } } // --------------------------------------------------------------- public class StartSPI implements IComm.IStart { @Override public String getStartCode() { return "SSS_PPP_III"; } } // --------------------------------------------------------------- public class StartUART implements IComm.IStart { @Override public String getStartCode() { return "UUU_AAA_RRR_TTT"; } } ``` 3. **`IFactory` 類**:定義抽象工廠,該工廠又依賴於 IProduct (抽象依賴抽象) :::info * 說明工廠的要產的目標產品,由於目標產品有分類,這邊我們也用抽象來描述工廠,細節交給實作類 ::: ```java= // 定義抽象工廠 public interface IFactory { IComm.ICreate create(); IComm.IStart getStartCode(); } ``` 4. **`ConcreateFactoryA`、`B`** :定義工廠實做,這裡我們特別定義了一個混核通訊,代表抽象工廠關注了具體產品,來產生不同維度的產品 ```java= // 定義工廠實做 public class SPI_Factory implements IFactory { @Override public IComm.ICreate create() { return new CreateSPI(); } @Override public IComm.IStart getStartCode() { return new StartSPI(); } } // --------------------------------------------------------------- public class UART_Factory implements IFactory { @Override public IComm.ICreate create() { return new CreateUART(); } @Override public IComm.IStart getStartCode() { return new StartUART(); } } // --------------------------------------------------------------- // 混合通訊 public class MIX_Factory implements IFactory { @Override public IComm.ICreate create() { return new CreateSPI(); } @Override public IComm.IStart getStartCode() { return new StartUART(); } } ``` * **User 使用抽象工廠**: SPI & UART 實作方式不同 (對於同一種操作有不同反應),這時就可以使用抽象類去拓展,製作出相同動作,但不同細節的方式 ```java= // User 直接使用工廠 public class FactoryTest { public static void main(String[] args) { FactoryTest factoryTest = new FactoryTest(); // 由工廠來產生不同目標 factoryTest.testFactory(new SPI_Factory()); factoryTest.testFactory(new UART_Factory()); factoryTest.testFactory(new MIX_Factory()); } public void testFactory(IFactory factory) { System.out.println("Factory implement: " + factory.getClass().getSimpleName()); IComm.ICreate iCreate = factory.create(); iCreate.create(); IComm.IStart startCode = factory.getStartCode(); System.out.println("Comm start code: " + startCode.getStartCode()); System.out.println("// -------------------------------\n"); } } ``` **--實作--** > ![](https://i.imgur.com/5ryEZOI.png) ### Abstract Factory 變型- 反射 * 我們在 Factory 時也有使用到反射工廠,相同的在抽象工廠中也可以使用,概念是差不多的,但這裡會創建更多工廠,看看反射工廠可以有那些好處 1. **`IProudct`** A、B:這部分沒有變動,仍是要生產的目標產品 (抽象) :::info * 說明產品的共同性,而這個共通性我們就可以使用抽象來表達 ::: ```java= public interface IComm { // 產品 1 interface ICreate { void create(); } // 產品 2 interface IStart { String getStartCode(); } } ``` 2. **`ConcreateProdcut`** A1、A2、B1、B2:這部分沒有變動,定義產品的實做 ```java= // 定義產品的實做 public class CreateSPI implements IComm.ICreate { @Override public void create() { System.out.println("Create SPI comm."); } } // --------------------------------------------------------------- public class CreateUART implements IComm.ICreate { @Override public void create() { System.out.println("Create UART comm."); } } // --------------------------------------------------------------- public class StartSPI implements IComm.IStart { @Override public String getStartCode() { return "SSS_PPP_III"; } } // --------------------------------------------------------------- public class StartUART implements IComm.IStart { @Override public String getStartCode() { return "UUU_AAA_RRR_TTT"; } } ``` 3. **`IFactory`**:這裡要做修改,**接收一個目標 class,透過 class 來創建目標類** ```java= public interface IFactory { // 接收目標 class IComm.ICreate create(Class<? extends IComm.ICreate> clz); // 接收目標 class IComm.IStart getStartCode(Class<? extends IComm.IStart> clz); } ``` 4. **`ConcreateFactory`**:這裡要做修改,**收到目標 class 後,用反射創建目標類,如果錯誤也會拋出** ```java= public class UniversalFactory implements IFactory { @Override public IComm.ICreate create(Class<? extends IComm.ICreate> clz) { IComm.ICreate result; try { Constructor<? extends IComm.ICreate> constructor = clz.getConstructor(); result = constructor.newInstance(); } catch (Exception e) { throw new RuntimeException(e); } return result; } @Override public IComm.IStart getStartCode(Class<? extends IComm.IStart> clz) { IComm.IStart result; try { Constructor<? extends IComm.IStart> constructor = clz.getConstructor(); result = constructor.newInstance(); } catch (Exception e) { throw new RuntimeException(e); } return result; } } ``` * User 使用:透過只用者指定細節 ```java= public class FactoryTest { public static void main(String[] args) { FactoryTest factoryTest = new FactoryTest(); IFactory factory = new UniversalFactory(); System.out.println("SPI ------------------------------------"); factoryTest.testFactory(factory, CreateSPI.class, StartSPI.class); System.out.println("UART -------------------------------\n"); factoryTest.testFactory(factory, CreateUART.class, StartUART.class); System.out.println("MIX -------------------------------\n"); factoryTest.testFactory(factory, CreateSPI.class, StartUART.class); } public void testFactory(IFactory factory, Class<? extends IComm.ICreate> create, Class<? extends IComm.IStart> start) { System.out.println("Factory implement: " + factory.getClass().getSimpleName()); IComm.ICreate iCreate = factory.create(create); iCreate.create(); IComm.IStart startCode = factory.getStartCode(start); System.out.println("Comm start code: " + startCode.getStartCode()); } } ``` :::warning * 在這裡 **不符合最少知識原則**,因為使用者必須知道自己要使用的細節 ::: > ![](https://i.imgur.com/ndVPBke.png) **--實作--** > ![](https://i.imgur.com/YF83u8l.png) :::warning * **變型反射有幾個缺點** ^1.^ 客戶端需知道具體細節由誰達成(因為必須指定 class 類),並且雖然透過反射可以減少類的生成,但是相對的 ^2.^反應時間較慢 ::: ## Android Source MediaPlayer ### [MediaPlayerFactory](https://cs.android.com/android/platform/superproject/+/master:frameworks/av/media/libmediaplayerservice/MediaPlayerFactory.h) * MediaPlayerFactory 會為不同 定義不同的類,而每種不同的 MediaPlayer 最終都會調用到 MediaPlayerFactory#registerFactory 方法 | 類 | 角色 | | -------- | -------- | | IFactory | 抽象工廠 | | NuPlayerFactory、TestPlayerFactory | 抽象實體工廠 | | [**MediaPlayerBase**](https://cs.android.com/android/platform/superproject/+/master:frameworks/av/media/libmediaplayerservice/include/MediaPlayerInterface) | 抽象產品 (IProduct) | | NuPlayerDriver、TestPlayerStub | 實際產品 | | MediaPlayerFactory | 使用者接觸的工廠入口,組裝抽象工廠、產品 | > ![](https://i.imgur.com/gn8zshp.png) * 抽象工廠 IFactory: `IFactory` 與抽象產品 `MediaPlayerBase` 有關 ```cpp= class IFactory { public: virtual ~IFactory() { } ... 省略部分 virtual float scoreFactory(const sp<IMediaPlayer>& /*client*/, const sp<DataSource> &/*source*/, float /*curScore*/) { return 0.0; } virtual sp<MediaPlayerBase> createPlayer(pid_t pid) = 0; }; ``` * 抽象產品 IProduct:`MediaPlayerBase` 定義應該要產的生的產品,細節交給子類 ```java= class MediaPlayerBase : public RefBase { ... virtual status_t initCheck() = 0; virtual bool hardwareOutput() = 0; virtual status_t setDataSource( const sp<IMediaHTTPService> &httpService, const char *url, const KeyedVector<String8, String8> *headers = NULL) = 0; virtual status_t setDataSource(int fd, int64_t offset, int64_t length) = 0; ... 省略部分 } ``` * MediaPlayerFactory 是工廠的使用入口,定義如何組裝抽象工廠(IFactory)、抽象產品(MediaPlayerBase) ```java= // MediaPlayerFactory.h class MediaPlayerFactory { public: // 宣告 static sp<MediaPlayerBase> createPlayer(player_type playerType, const sp<MediaPlayerBase::Listener> &listener, pid_t pid); } // ---------------------------------------------------------- // MediaPlayerFactory.cpp MediaPlayerFactory::tFactoryMap MediaPlayerFactory::sFactoryMap; sp<MediaPlayerBase> MediaPlayerFactory::createPlayer( player_type playerType, // Player 類型 const sp<MediaPlayerBase::Listener> &listener, // 監聽器 pid_t pid) { sp<MediaPlayerBase> p; IFactory* factory; status_t init_result; Mutex::Autolock lock_(&sLock); if (sFactoryMap.indexOfKey(playerType) < 0) { ... 尚未註冊 return p; } // 決定 factory 的實作類 factory = sFactoryMap.valueFor(playerType); CHECK(NULL != factory); // 呼叫抽象工廠 createPlayer 方法 p = factory->createPlayer(pid); if (p == NULL) { ... 省略失敗 log return p; } // 呼叫抽象產品 initCheck 方法 init_result = p->initCheck(); if (init_result == NO_ERROR) { p->setNotifyCallback(listener); } else { ... 省略失敗 log p.clear(); } return p; } ``` ### MediaPlayer 註冊工廠 * 我們知道使用者會透過 MediaPlayerFactory 來使用 IFactory (抽象工廠),而 IFactory 的實作有 `NuPlayerFactory`、`TestPlayerFactory` 兩個類,這兩個類會在 MediaPlayerFactory 建構函數時被註冊 1. **`MediaPlayerFactory` 建構函數**:註冊 MediaPlayerBase 時做 ```java= // MediaPlayerService.java MediaPlayerService::MediaPlayerService() { ALOGV("MediaPlayerService created"); mNextConnId = 1; // @ 追蹤 registerBuiltinFactories 方法 MediaPlayerFactory::registerBuiltinFactories(); } ``` 2. **`registerBuiltinFactories` 方法**:創建 `NuPlayerFactory`、`TestPlayerFactory` 實體工廠,並透過 `registerFactory_l` 方法註冊工廠 ```java= // MediaPlayerService.java Mutex MediaPlayerFactory::sLock; bool MediaPlayerFactory::sInitComplete = false; void MediaPlayerFactory::registerBuiltinFactories() { Mutex::Autolock lock_(&sLock); if (sInitComplete) // 避免反覆註冊 return; IFactory* factory = new NuPlayerFactory(); if (registerFactory_l(factory, NU_PLAYER) != OK) delete factory; // 創建失敗則解構 factory = new TestPlayerFactory(); if (registerFactory_l(factory, TEST_PLAYER) != OK) delete factory; // 創建失敗則解構 sInitComplete = true; } ``` 3. **`registerFactory_l` 方法**:將 IFactory 物件暫存在 MediaPlayerFactory 中,方便在下是需要時快速返回,避免建構重複物件 ```java= MediaPlayerFactory::tFactoryMap MediaPlayerFactory::sFactoryMap; status_t MediaPlayerFactory::registerFactory_l(IFactory* factory, player_type type) { if (NULL == factory) { ... 判空 return BAD_VALUE; } // 查看是否已經註冊 if (sFactoryMap.indexOfKey(type) >= 0) { ... 已註冊 return ALREADY_EXISTS; } // 添加緩存 if (sFactoryMap.add(type, factory) < 0) { ...添加進緩存失敗 return UNKNOWN_ERROR; } return OK; } ``` ## 更多的物件導向設計 物件導向的設計基礎如下,如果是初學者或是不熟悉的各位,建議可以從這些基礎開始認識,打好基底才能走個更穩(在學習的時候也需要不斷回頭看)! :::info * [**設計建模 2 大概念- UML 分類、使用**](https://devtechascendancy.com/introduction-to-uml-and-diagrams/) * [**物件導向設計原則 – 6 大原則(一)**](https://devtechascendancy.com/object-oriented-design-principles_1/) * [**物件導向設計原則 – 6 大原則(二)**](https://devtechascendancy.com/object-oriented-design-principles_2/) ::: ### 創建模式 - Creation Patterns * [**創建模式 PK**](https://devtechascendancy.com/pk-design-patterns-factory-builder-best/) * **創建模式 - `Creation Patterns`**: 創建模式用於「**物件的創建**」,它關注於如何更靈活、更有效地創建對象。這些模式可以隱藏創建對象的細節,並提供創建對象的機制,例如單例模式、工廠模式… 等等,詳細解說請點擊以下連結 :::success * [**Singleton 單例模式 | 解說實現 | Android Framework Context Service**](https://devtechascendancy.com/object-oriented_design_singleton/) * [**Abstract Factory 設計模式 | 實現解說 | Android MediaPlayer**](https://devtechascendancy.com/object-oriented_design_abstract-factory/) * [**Factory 工廠方法模式 | 解說實現 | Java 集合設計**](https://devtechascendancy.com/object-oriented_design_factory_framework/) * [**Builder 建構者模式 | 實現與解說 | Android Framwrok Dialog 視窗**](https://devtechascendancy.com/object-oriented_design_builder_dialog/) * [**Clone 原型模式 | 解說實現 | Android Framework Intent**](https://devtechascendancy.com/object-oriented_design_clone_framework/) * [**Object Pool 設計模式 | 實現與解說 | 利用 JVM**](https://devtechascendancy.com/object-oriented_design_object-pool/) * [**Flyweight 享元模式 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_flyweight/) ::: ### 行為模式 - Behavioral Patterns * [**行為模式 PK**](https://devtechascendancy.com/pk-design-patterns-cmd-strat-state-obs-chain/) * **行為模式 - `Behavioral Patterns`**: 行為模式關注物件之間的「**通信**」和「**職責分配**」。它們描述了一系列對象如何協作,以完成特定任務。這些模式專注於改進物件之間的通信,從而提高系統的靈活性。例如,策略模式、觀察者模式… 等等,詳細解說請點擊以下連結 :::warning * [**Stragety 策略模式 | 解說實現 | Android Framework 動畫**](https://devtechascendancy.com/object-oriented_design_stragety_framework/) * [**Interpreter 解譯器模式 | 解說實現 | Android Framework PackageManagerService**](https://devtechascendancy.com/object-oriented_design_interpreter_framework/) * [**Chain 責任鏈模式 | 解說實現 | Android Framework View 事件傳遞**](https://devtechascendancy.com/object-oriented_design_chain_framework/) * [**State 狀態模式 | 實現解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_state/) * [**Specification 規格模式 | 解說實現 | Query 語句實做**](https://devtechascendancy.com/object-oriented_design_specification-query/) * [**Command 命令、Servant 雇工模式 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_command_servant/) * [**Memo 備忘錄模式 | 實現與解說 | Android Framwrok Activity 保存**](https://devtechascendancy.com/object-oriented_design_memo_framework/) * [**Visitor 設計模式 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_visitor_dispatch/) * [**Template 設計模式 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_template/) * [**Mediator 模式設計 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_programming_mediator/) * [**Composite 組合模式 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_programming_composite/) ::: ### 結構模式 - Structural Patterns * [**結構模式 PK**](https://devtechascendancy.com/pk-design-patterns-proxy-decorate-adapter/) * **結構模式 - `Structural Patterns`**: 結構模式專注於「物件之間的組成」,以形成更大的結構。這些模式可以幫助你確保當系統進行擴展或修改時,不會破壞其整體結構。例如,外觀模式、代理模式… 等等,詳細解說請點擊以下連結 :::danger * [**Bridge 橋接模式 | 解說實現 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_bridge/) * [**Decorate 裝飾模式 | 解說實現 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_decorate/) * [**Proxy 代理模式 | 解說實現 | 分析動態代理**](https://devtechascendancy.com/object-oriented_design_proxy_dynamic-proxy/) * [**Iterator 迭代設計 | 解說實現 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_iterator/) * [**Facade 外觀、門面模式 | 解說實現 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_facade/) * [**Adapter 設計模式 | 解說實現 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_adapter/) ::: ## Appendix & FAQ :::info ::: ###### tags: `Java 設計模式` `基礎進階`