Overview of Content
如有引用參考請詳註出處,感謝
在這篇文章中,我們將深入探討 抽象工廠設計模式(Abstract Factory Design Pattern
)在 Android MediaPlayer 中的實現與解說。
抽象工廠也被稱之為 工具箱模式(
Kit Pattern
)
首先,我們會提供整體內容的概述,並深入了解抽象工廠的使用場景。接著,我們將解釋抽象工廠的定義,並透過UML結構呈現其模型。在設計方面,我們會詳細討論抽象工廠模式的優缺點,使讀者能全面了解其適用情境。
在實際實做部分,我們將深入探討抽象工廠的標準實現方式,並討論其變型,包括使用反射的實現方式。
隨後,我們將轉向 Android 源碼中的 MediaPlayer,介紹 MediaPlayerFactory 以及 MediaPlayer 註冊工廠的實作細節。透過本文,讀者將能深入了解抽象工廠設計模式在 Android MediaPlayer 中的實際應用。
Abstract Factory 使用場景
Factory 工廠方法 的升級版,抽象工廠多了依賴介面,多型拓展並依賴其抽象
● 一個 物件集合有 相同約束(功能),但實作方法不同時,即可使用
就像是 Window 系統、Mac 系統 都有藍芽功能(Factory),但掃描、連接、傳輸...實做都不同(Product),這就可以使用抽象工廠模式
Abstract Factory 定義 & UML
● Abstract Factory
定義:
為創建一組 相關 或 相互依賴 的對象 提供一個抽象接口,而 不指定它們的具體類
● 工廠模式 vs 抽象工廠模式
● 相同點
工廠模式、抽象工廠的建構對象都在內部,使用者不用關心實際對象,而是關注界面(契約)
● 不同點:兩者對於生產的目的有明顯的不同
工廠模式中,使用者會關注
Factory
產生的Product
,產品與工廠的關係不大工廠模式生產 一個產品,這個產品有契約上定義的行為操作,但產品有不同特色(實作)
抽象工廠則是
Product
與Factory
有一定的關聯性 (關係更重),使用不同的Product
會造出不同產品抽象工廠生產多個產品(一系列產品),一組契約等著被實現,而各個產品也可以有不同特點
● 抽象工廠手法的不同點
抽象工廠將工廠主體類別中的抽象產品「提煉為一個產品界面」,用物件合成(Object Composition) 取代了類別繼承,所以會更加的靈活,也更加的抽象、複雜
● 抽象工廠 UML:抽象工廠的 重點是在抽象 Factory 上依賴其他抽象 Product,透過操控工廠產生不同產品
類 | 功能 |
---|---|
IFactory (抽象) | 依賴抽象 Product、所以產生的產品也是抽象 |
ConcreateFactory_A、B | 實做工廠類,藉由抽象產品,創建目標類 |
IProductA、B (抽象) | 抽象產品,也就是各個產品的行為契約 |
ConcreateProdcut_A1 、A2 、B1 、B2 | 定義抽象產品的細節 |
● 一般來講有多少 Product 就對應有多少 Factory
Abstract Factory 設計:優缺點
● Abstract Factory 設計優點
● 具體類別利用 抽像解偶,基於介面與實作的分離,由繼承者實作方法
● 可以使用反射方式可以減少實作工廠類
● 但使用反射技巧後,相對的使用者必須知道細節,這不符合
最少知識原則
,在使用時要自己做些取捨
● Abstract Factory 設計缺點
● Product 新增的功能,代表所有類都要新增這項功能,不符合開閉原則 (這也是里式原則的缺點);可以使用 介面隔離
原則,讓介面立度最小化
● 上面所說的是直向拓展(繼承)的
Product
會影響到各個子類別;但橫向拓展的Factory
就不會受到這個影響
Abstract Factory 實做
以下我們實現一組通訊協議
● 這組通訊協議作為產品(Product
)需要提供創建通訊物件(create
)、取得起始碼(getStartCode
)這兩個功能
● 而實作的工廠(Factory
)需要提供使用者 SPI
、UART
、MIX
通訊協議
Abstract Factory 標準
A. IProduct A
、B
類 :首先決定抽象產品,這邊我們要定義一組通訊協定,這個協定有 Create、Start code ... 等等規範,但是實際要由通訊方案決定
● 說明產品的共同性,而這個共通性我們就可以使用抽象來表達
// 抽象產品
public interface IComm {
// 產品 1
interface ICreate {
void create();
}
// 產品 2
interface IStart {
String getStartCode();
}
}
B. ConcreateProdcutA1
、A2
、B1
、B2
類:定義產品的實做
// 定義產品的實做
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";
}
}
C. IFactory
類:定義抽象工廠,該工廠又依賴於 IProduct (抽象依賴抽象)
● 說明工廠的要產的目標產品,由於目標產品有分類,這邊我們也用抽象來描述工廠,細節交給實作類
// 定義抽象工廠
public interface IFactory {
IComm.ICreate create();
IComm.IStart getStartCode();
}
D. ConcreateFactoryA
、B
:定義工廠實做,這裡我們特別定義了一個混核通訊,代表抽象工廠關注了具體產品,來產生不同維度的產品
// 定義工廠實做
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 實作方式不同 (對於同一種操作有不同反應),這時就可以使用抽象類去拓展,製作出相同動作,但不同細節的方式
// 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 時也有使用到反射工廠,相同的在抽象工廠中也可以使用,概念是差不多的,但這裡會創建更多工廠,看看反射工廠可以有那些好處
A. IProudct
A、B:這部分沒有變動,仍是要生產的目標產品 (抽象)
● 說明產品的共同性,而這個共通性我們就可以使用抽象來表達
public interface IComm {
// 產品 1
interface ICreate {
void create();
}
// 產品 2
interface IStart {
String getStartCode();
}
}
B. ConcreateProdcut
A1、A2、B1、B2:這部分沒有變動,定義產品的實做
// 定義產品的實做
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";
}
}
C. IFactory
:這裡要做修改,接收一個目標 class,透過 class 來創建目標類
public interface IFactory {
// 接收目標 class
IComm.ICreate create(Class<? extends IComm.ICreate> clz);
// 接收目標 class
IComm.IStart getStartCode(Class<? extends IComm.IStart> clz);
}
D. ConcreateFactory
:這裡要做修改,收到目標 class 後,用反射創建目標類,如果錯誤也會拋出
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 使用:透過只用者指定細節
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());
}
}
● 在這裡 不符合最少知識原則,因為使用者必須知道自己要使用的細節
--實作--
● 變型反射有幾個缺點
1. 客戶端需知道具體細節由誰達成(因為必須指定 class 類),並且雖然透過反射可以減少類的生成,但是相對的 2.反應時間較慢
Android Source MediaPlayer
● MediaPlayerFactory 會為不同 定義不同的類,而每種不同的 MediaPlayer 最終都會調用到 MediaPlayerFactory#registerFactory 方法
類 | 角色 |
---|---|
IFactory | 抽象工廠 |
NuPlayerFactory、TestPlayerFactory | 抽象實體工廠 |
MediaPlayerBase | 抽象產品 (IProduct) |
NuPlayerDriver、TestPlayerStub | 實際產品 |
MediaPlayerFactory | 使用者接觸的工廠入口,組裝抽象工廠、產品 |
● 抽象工廠 IFactory: IFactory
與抽象產品 MediaPlayerBase
有關
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
定義應該要產的生的產品,細節交給子類
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)
// 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 建構函數時被註冊
A. MediaPlayerFactory
建構函數:註冊 MediaPlayerBase 時做
// MediaPlayerService.java
MediaPlayerService::MediaPlayerService()
{
ALOGV("MediaPlayerService created");
mNextConnId = 1;
// @ 追蹤 registerBuiltinFactories 方法
MediaPlayerFactory::registerBuiltinFactories();
}
B. registerBuiltinFactories
方法:創建 NuPlayerFactory
、TestPlayerFactory
實體工廠,並透過 registerFactory_l
方法註冊工廠
// 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;
}
C. registerFactory_l
方法:將 IFactory 物件暫存在 MediaPlayerFactory 中,方便在下是需要時快速返回,避免建構重複物件
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;
}
更多的物件導向設計
物件導向的設計基礎如下,如果是初學者或是不熟悉的各位,建議可以從這些基礎開始認識,打好基底才能走個更穩(在學習的時候也需要不斷回頭看)!
創建模式 Creation Patterns
● 創建模式 PK
● 創建模式 - Creation Patterns
:
創建模式用於「物件的創建」,它關注於如何更靈活、更有效地創建物件。這些模式可以隱藏創建物件的細節,並提供創建物件的機制,例如單例模式、工廠模式… 等等,詳細解說請點擊以下連結
● Singleton 單例模式 | 解說實現 | Android Framework Context Service
● Abstract Factory 設計模式 | 實現解說 | Android MediaPlayer
● Factory 工廠方法模式 | 解說實現 | Java 集合設計
● Builder 建構者模式 | 實現與解說 | Android Framwrok Dialog 視窗
● Clone 原型模式 | 解說實現 | Android Framework Intent
行為模式 Behavioral Patterns
● 行為模式 PK
● 行為模式 - Behavioral Patterns
:
行為模式關注物件之間的「通信」和「職責分配」。它們描述了一系列物件如何協作,以完成特定任務。這些模式專注於改進物件之間的通信,從而提高系統的靈活性。例如,策略模式、觀察者模式… 等等,詳細解說請點擊以下連結
● Stragety 策略模式 | 解說實現 | Android Framework 動畫
● Interpreter 解譯器模式 | 解說實現 | Android Framework PackageManagerService
● Chain 責任鏈模式 | 解說實現 | Android Framework View 事件傳遞
● Specification 規格模式 | 解說實現 | Query 語句實做
● Command 命令、Servant 雇工模式 | 實現與解說 | 物件導向設計
● Memo 備忘錄模式 | 實現與解說 | Android Framwrok Activity 保存
● Visitor 設計模式 | 實現與解說 | 物件導向設計
● Template 設計模式 | 實現與解說 | 物件導向設計
● Mediator 模式設計 | 實現與解說 | 物件導向設計
● Composite 組合模式 | 實現與解說 | 物件導向設計
● Observer 觀察者模式 | JDK Observer | Android Framework Listview
結構模式 Structural Patterns
● 結構模式 PK
● 結構模式 - Structural Patterns
:
結構模式專注於「物件之間的組成」,以形成更大的結構。這些模式可以幫助你確保當系統進行擴展或修改時,不會破壞其整體結構。例如,外觀模式、代理模式… 等等,詳細解說請點擊以下連結
● Decorate 裝飾模式 | 解說實現 | 物件導向設計
● Iterator 迭代設計 | 解說實現 | 物件導向設計