Abstract Factory 設計模式 | 實現解說 | Android MediaPlayer

Abstract Factory 設計模式 | 實現解說 | Android MediaPlayer

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,產品與工廠的關係不大

工廠模式生產 一個產品,這個產品有契約上定義的行為操作,但產品有不同特色(實作)

抽象工廠則是 ProductFactory 有一定的關聯性 (關係更重),使用不同的 Product 會造出不同產品

抽象工廠生產多個產品(一系列產品),一組契約等著被實現,而各個產品也可以有不同特點

抽象工廠手法的不同點

抽象工廠將工廠主體類別中的抽象產品「提煉為一個產品界面」,用物件合成(Object Composition) 取代了類別繼承,所以會更加的靈活,也更加的抽象、複雜

● 抽象工廠 UML:抽象工廠的 重點是在抽象 Factory 上依賴其他抽象 Product,透過操控工廠產生不同產品

功能
IFactory (抽象)依賴抽象 Product、所以產生的產品也是抽象
ConcreateFactory_A、B實做工廠類,藉由抽象產品,創建目標類
IProductA、B (抽象)抽象產品,也就是各個產品的行為契約
ConcreateProdcut_A1A2B1B2定義抽象產品的細節

● 一般來講有多少 Product 就對應有多少 Factory

Abstract Factory 設計:優缺點

● Abstract Factory 設計優點

● 具體類別利用 抽像解偶,基於介面與實作的分離,由繼承者實作方法

● 可以使用反射方式可以減少實作工廠類

● 但使用反射技巧後,相對的使用者必須知道細節,這不符合 最少知識原則,在使用時要自己做些取捨

● Abstract Factory 設計缺點

Product 新增的功能,代表所有類都要新增這項功能,不符合開閉原則 (這也是里式原則的缺點);可以使用 介面隔離 原則,讓介面立度最小化

● 上面所說的是直向拓展(繼承)的 Product 會影響到各個子類別;但橫向拓展的 Factory 就不會受到這個影響


Abstract Factory 實做

以下我們實現一組通訊協議

● 這組通訊協議作為產品(Product)需要提供創建通訊物件(create)、取得起始碼(getStartCode)這兩個功能

● 而實作的工廠(Factory)需要提供使用者 SPIUARTMIX 通訊協議

Abstract Factory 標準

A. IProduct AB :首先決定抽象產品,這邊我們要定義一組通訊協定,這個協定有 Create、Start code ... 等等規範,但是實際要由通訊方案決定

● 說明產品的共同性,而這個共通性我們就可以使用抽象來表達


// 抽象產品

public interface IComm {

    // 產品 1
    interface ICreate {
        void create();
    }

    // 產品 2
    interface IStart {
        String getStartCode();
    }
}

B. ConcreateProdcutA1A2B1B2:定義產品的實做


// 定義產品的實做

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. ConcreateFactoryAB :定義工廠實做,這裡我們特別定義了一個混核通訊,代表抽象工廠關注了具體產品,來產生不同維度的產品


// 定義工廠實做

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

● 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 的實作有 NuPlayerFactoryTestPlayerFactory 兩個類,這兩個類會在 MediaPlayerFactory 建構函數時被註冊

A. MediaPlayerFactory 建構函數:註冊 MediaPlayerBase 時做


// MediaPlayerService.java

MediaPlayerService::MediaPlayerService()
{
    ALOGV("MediaPlayerService created");
    mNextConnId = 1;

    // @ 追蹤 registerBuiltinFactories 方法
    MediaPlayerFactory::registerBuiltinFactories();
}

B. registerBuiltinFactories 方法:創建 NuPlayerFactoryTestPlayerFactory 實體工廠,並透過 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;
}

更多的物件導向設計

物件導向的設計基礎如下,如果是初學者或是不熟悉的各位,建議可以從這些基礎開始認識,打好基底才能走個更穩(在學習的時候也需要不斷回頭看)!

設計建模 2 大概念- UML 分類、使用

物件導向設計原則 – 6 大原則(一)

物件導向設計原則 – 6 大原則(二)

創建、行為、結構型設計 8 個比較 | 包裝模式 | 最佳實踐

創建模式 Creation Patterns

創建模式 PK

創建模式 - Creation Patterns

結構模式 Structural Patterns

結構模式 PK

結構模式 - Structural Patterns

結構模式專注於「物件之間的組成」,以形成更大的結構。這些模式可以幫助你確保當系統進行擴展或修改時,不會破壞其整體結構。例如,外觀模式、代理模式… 等等,詳細解說請點擊以下連結

Bridge 橋接模式 | 解說實現 | 物件導向設計

Decorate 裝飾模式 | 解說實現 | 物件導向設計

Proxy 代理模式 | 解說實現 | 分析動態代理

Iterator 迭代設計 | 解說實現 | 物件導向設計

Facade 外觀、門面模式 | 解說實現 | 物件導向設計

Adapter 設計模式 | 解說實現 | 物件導向設計

Leave a Comment

Comments

No comments yet. Why don’t you start the discussion?

發表迴響