---
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` | 定義抽象產品的細節 |
> 
:::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");
}
}
```
**--實作--**
> 
### 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
* 在這裡 **不符合最少知識原則**,因為使用者必須知道自己要使用的細節
:::
> 
**--實作--**
> 
:::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 | 使用者接觸的工廠入口,組裝抽象工廠、產品 |
> 
* 抽象工廠 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 設計模式` `基礎進階`