Decorate 裝飾模式 | 解說實現 | Android Framework Context

Decorate 裝飾模式 | 解說實現 | Android Framework Context

Overview of Content

在這篇文章中,我們將深入探討 裝飾模式(Decorator Pattern在物件導向設計中的實際應用。首先,我們會總覽整體內容,接著介紹裝飾模式的使用場景。然後,我們將深入了解裝飾模式的定義,以及透過裝飾模式的UML結構

隨後,我們會探討裝飾模式在設計上的優缺點。在進入實作部分後,我們會深入探討裝飾模式的標準實現方式。這將使讀者更加熟悉裝飾模式的基本實踐方法

最後,我們帶入到 Android Source 對裝飾模式實際的使用,也就是「Context」類的設計,來看看源碼中如何善用裝飾模式來達到加強 Context 的功能

寫文章分享不易,如有引用參考請詳註出處,如有指導、意見歡迎留言(如果覺得寫得好也請給我一些支持),感謝 😀


Decorate 使用場景

裝飾模式,在不改變其繼承類別動態拓展功能的方法,==繼承的替代方案之一==裝飾模式比繼承生成子類更靈活

它的實現可參考 Java's IO 流、Android 的第三方庫 RxJava

A. 當不能使用繼承或是使用了繼承後不利於維護時可以使用

當繼承超過 2 層的時候,其實程式會難以維護,這時其實就可以重新檢視程式,並考慮轉換設計

B. 以不影響其他類別的方式 動態的使用(動態新增、移除功能)、撤銷其類別,而不必依靠覆寫 (Override)

繼承是靜態的給類新增功能、裝飾則是動態的給類新增功能

C. 對於原先的類做加裝、改裝功能(並不影響原先的類)

Decorate 定義 & Decorate UML

Decorate 定義

動態的給一個物件添加(減少)額外功能,裝飾模式相比繼承更加靈活

Decorate 角色介紹

角色功能說明
Component可以是一個抽象,或是接口,它是最原始的對象(有最基礎、最核心的方法)
ConcreateComponent具體實現類,也是等待被裝飾(被代理)者,它實現了最基礎的方法
Decorator1. 繼承 Component 類 並 2. 依賴抽象(Component 類)取代繼承,內部持有一個抽象 Component 對象
ConcreateDecorator具體裝飾者,你要它組裝結合 ConcreateComponent 的實做一起表現出來

依靠注入抽象模組(高層模組),使用別類(低層模組)實現功能

Decorate 設計:優缺點

Decorate 設計優點 :

A. 可以減少里式原則帶來的,繼承時帶來的負荷 (一定要複寫該類 or 類別方法增多),子類無限擴張等問題

當然你需要切細每個類的方法

B. 用戶可依據需求使用,而不改變該類的方法,符合開閉原則

子類可以單獨發展,而不必受到繼承約束

C. 裝飾、被裝飾類都可以獨立發展!不會相互依賴~ (裝飾類依賴的是抽象)

Decorate 設計缺點 :

A. 高層模組發生變化時勢必會影響到繼承的所有子類別

B. 除錯 Debug 會更困難,因為 層層嵌套,邏輯變複雜 (可讀性降低)

C. 多層裝飾會導致效率變差 (仍是層層傳遞的問題)


Decorate 實現

Decorate 標準實現

A. Component:IWear 擁有最基礎、核心的方法抽象


interface IWear {

    fun wearClothes()

}

B. ConcreateComponent:待被裝飾的類(也可以說待被代理)


class NoWorkPerson : IWear {

    override fun wearClothes() {
        println("Wear Underwear")
    }

}

C. Decorator:繼承於 IWear 類,並持有一個 IWear 對象


abstract class DecorClothes constructor(val realWear: IWear) : IWear {

    override fun wearClothes() {
        realWear.wearClothes()
    }

}

D. ConcreateDecorator:這個類的重點是在於實現你自己的邏輯,你要如何裝飾傳入的實際類 (這裡創建兩個裝飾類)


class WorkPerson constructor(realWear: IWear): DecorClothes(realWear) {

    // 裝飾方法
    private fun wearLittle() {
        println("Wear shoes")
    }

    override fun wearLittle() {
        wearLittle()

        println("--- WorkPerson finish decor")
        super.wearClothes()
    }

}


class WorkHardPerson constructor(realWear: IWear): DecorClothes(realWear) {

    // 裝飾方法
    private fun wearMore() {
        println("Wear T-short")
        println("Wear paint")
    }

    override fun wearClothes() {
        wearMore()

        println("--- WorkHardPerson finish decor")
        super.wearClothes()
    }

}    

● 作為使用者方,使用裝飾模式:


fun main() {
    
    // 一層一層裝飾,最終返回最後一個裝飾者
    val finalMan = NoWorkPerson().let { noWorkPerson ->
        WorkPerson(noWorkPerson)
    }.let { workPerson ->
        WorkHardPerson(workPerson)
    }

    
    // 其實最終 finalMan 類型是 WorkHardPerson
    finalMan.wearClothes()

}

Android Source Context 分析

Context 物件以說是 Android 開發者最熟悉,也是最不熟悉的物件了,它應用於 Android 4 大組件中(ActivityServiceContentProviderBroadcast),而與 Context 最直接關聯、也最常見的就是 Activity 跟 Application

而 Context 本身是個抽象類,在使用時其實也用到了裝飾模式


// Context.java

public abstract class Context {
    ... 省略
}

以下分析的是 Android 10 的源碼

Context & ContextImpl 的裝飾角色

● 我們這邊先直接揭露 Context & ContextImpl 在裝飾模式下的角色,再進行分析

裝飾模式 Prototype 角色Android 實做類功能說明
ComponentContext可以是一個抽象,或是接口,它是最原始的物件(有最基礎、最核心的方法)
ConcreateComponentContextImpl具體實現類,也是等待被裝飾(被代理)者,它實現了最基礎的方法
DecoratorContextImplContextWrapper1. 繼承 Component 類 並 2. 依賴抽象(Component 類)取代繼承,內部持有一個抽象 Component 物件

Android Context 實現裝置模式的 UML 概念圖如下

image

Context & ContextImpl 裝飾結構分析

● Context 類作為裝飾模式中抽象的「原始物件」,它並不實做,但擁有許多核心的抽象方法,其中就包括 資源管理文件管理包管理類加載權限管理提供系統服務… 等等功能,我們依照 4 大組件的方向去看看 Context 與其的重要性

A. 裝飾模式下 Context 是作為 Component 角色

● 最常見 Context 提供「Activity」的相關功能如下


// Context.java

public abstract class Context {
    ... 省略

    // 取得應用的訊息
    public abstract ApplicationInfo getApplicationInfo();

    // 取得 Application 的 Context
    public abstract Context getApplicationContext();

    // 啟動 Activity
    public abstract void startActivity(@RequiresPermission Intent intent);

    public abstract void startActivity(@RequiresPermission Intent intent,
    @Nullable Bundle options);


    // 啟動多個 Activity
    public abstract void startActivities(@RequiresPermission Intent[] intents);

    public abstract void startActivities(@RequiresPermission Intent[] intents, Bundle options);
}

● 最常見 Context 提供「Service」的相關功能如下


// Context.java

public abstract class Context {
    ... 省略

    // 啟動 Service
    @Nullable
    public abstract ComponentName startService(Intent service);

    // 停止 Service
    public abstract boolean stopService(Intent service);


    // 應用綁定 Service
    public abstract boolean bindService(@RequiresPermission Intent service,
    @NonNull ServiceConnection conn, @BindServiceFlags int flags);

    // 解綁定 Service       
    public abstract void unbindService(@NonNull ServiceConnection conn);
}

● 最常見 Context 提供「Broadcast」的相關功能如下


// Context.java

public abstract class Context {
    ... 省略

    // 註冊一個廣播接收者
    @Nullable
    public abstract Intent registerReceiver(@Nullable BroadcastReceiver receiver,
                                            IntentFilter filter);

    @Nullable    
    public abstract Intent registerReceiver(@Nullable BroadcastReceiver receiver,
                                            IntentFilter filter,
                                            @RegisterReceiverFlags int flags);

    // 解註冊廣播接收者
    public abstract void unregisterReceiver(BroadcastReceiver receiver);

    // 發送廣播
    public abstract void sendBroadcast(@RequiresPermission Intent intent);     


    // 發送帶有權限的廣播            
    public abstract void sendBroadcast(@RequiresPermission Intent intent,
    @Nullable String receiverPermission);

    public abstract void sendBroadcastMultiplePermissions(Intent intent,
    String[] receiverPermissions);


    // 發送順序廣播
    public abstract void sendOrderedBroadcast(@RequiresPermission Intent intent,
    @Nullable String receiverPermission);

    // 發送黏性廣播,這意味著 Intent 會保留在廣播內
    public abstract void sendStickyBroadcast(@RequiresPermission Intent intent);

    public abstract void sendStickyOrderedBroadcast(@RequiresPermission Intent intent,
            BroadcastReceiver resultReceiver,
            @Nullable Handler scheduler, int initialCode, @Nullable String initialData,
            @Nullable Bundle initialExtras);

}

● 最常見 Context 提供「ContentProvider」的相關功能如下


// Context.java

public abstract class Context {
    ... 省略

    // 為該應用取得一個 ContentResolver 物件
    public abstract ContentResolver getContentResolver();
}

● 最常見 Context 提供的重要功能:


// Context.java

public abstract class Context {
    ... 省略

    public abstract Looper getMainLooper();

    public abstract @Nullable Object getSystemService(@ServiceName     @NonNull String name);

    public abstract @Nullable Object getSystemService(@ServiceName @NonNull String name);

    public abstract SharedPreferences getSharedPreferences(String name, @PreferencesMode int mode);

    @Nullable
    public abstract File getExternalFilesDir(@Nullable String type);
}

B. 裝飾模式下 ContextImpl 類,既可作為作為 ConcreateComponent 也可作為 Decorator 角色,也就是它本身可以是個沒有裝飾功能的「實做」,也可以「裝飾類」

以下是 ContextImpl 的建構函數:我們可以看到,它是可以傳入 ContextImpl 類,而這個參數其實也就透漏個它是一個裝飾設計(也就是說可以加強、減弱、使用… 傳入的 ContextImpl 物件)


// ContextImpl.java

private ContextImpl(@Nullable ContextImpl container,
                    @NonNull ActivityThread mainThread,
                   ...) {

    ... 忽略部份
        
    if (container != null) {
        // 取外部 ContextImpl 物件來使用
        mBasePackageName = container.mBasePackageName;
        opPackageName = container.mOpPackageName;
        setResources(container.mResources);
        mDisplay = container.mDisplay;
    } else {
        ... 忽略部份
    }
    
    ... 忽略部份
}

ContextImpl 作為 Decorator 「實做」角色:創建 System Context 時會傳入 null,代表的是 不去裝飾(也就是 最外層的 Context


// ContextImpl.java

@UnsupportedAppUsage
static ContextImpl createSystemContext(ActivityThread mainThread) {
    LoadedApk packageInfo = new LoadedApk(mainThread);
    ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0,
            null, null);
    ... 忽略

    return context;
}

ContextImpl 作為 ConcreateComponent 「裝飾類」角色:創建 Applicatoin Context 時會傳入 this,代表 可以做裝飾(也就是某一層的 Context)


// ContextImpl.java

@Override
public Context createApplicationContext(ApplicationInfo application, int flags)
        throws NameNotFoundException {
    
    LoadedApk pi = mMainThread.getPackageInfo(application, mResources.getCompatibilityInfo(),
            flags | CONTEXT_REGISTER_PACKAGE);
    if (pi != null) {
        ContextImpl c = new ContextImpl(this, mMainThread, pi, null, mActivityToken,
                new UserHandle(UserHandle.getUserId(application.uid)), flags, null, null
        ... 忽略
    }

    throw new PackageManager.NameNotFoundException(
            "Application package " + application.packageName + " not found");
}

最明顯的裝飾 ContextWrapper

● 如果你還是沒辦法看出 Context 裝飾模式的使用的話,我們可以看看 ContextWrapper,從這個類中我們可以很清晰的看到 ContextWrapper 其實沒做甚麽事情,單純的是把外部傳入的 Context 保存起來,並在需要的時候呼叫使用

我們看看最常使用的 startActivity 類就可以發現,源碼如下


// ContextWrapper.java

public class ContextWrapper extends Context {
    
    // 外部傳入的 Context
    @UnsupportedAppUsage
    Context mBase;

    public ContextWrapper(Context base) {
        // 保存外部傳入的 Context
        mBase = base;
    }

    @Override
    public void startActivity(Intent intent) {
        // 直接呼叫外部傳入的 Context
        mBase.startActivity(intent);
    }
    
    ... 省略部份
}

Activity 綁定 Context 的重點

● 這邊我們進入 performLaunchActivity 函數:

● 這裡我們忽略 Android 系統是如何啟動的細節,直接看創建 Activity 的關鍵代碼,也就是從 ActivityThread#performLaunchActivity 開始看


// ActivityThread.java

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {

    ... 省略部份

    // 創建 ContextImpl 類
    ContextImpl appContext = createBaseContextForActivity(r);
    Activity activity = null;

    Activity activity = null;
    try {
        java.lang.ClassLoader cl = appContext.getClassLoader();
        // 創建目標 Activity
        activity = mInstrumentation.newActivity(
                cl, component.getClassName(), r.intent);


    } ... 省略 catch


    try {
        // 如果沒有 Application 的話,就創建 Application instacne
        Application app = r.packageInfo.makeApplication(false, mInstrumentation);

        ... 省略部份

        if (activity != null) {
            ... 省略部份

            // 連接 Context & Activity 的關係
            appContext.setOuterContext(activity);

            // 連接 Activity & Context & Application 類
            activity.attach(appContext, this, getInstrumentation(), r.token,
                    r.ident, app, r.intent, r.activityInfo, title, r.parent,
                    r.embeddedID, r.lastNonConfigurationInstances, config,
                    r.referrer, r.voiceInteractor, window, r.configCallback,
                    r.assistToken);

            ... 省略部份

            r.activity = activity;
        } 
    } ... 省略 catch

}

可以主要的相關源碼中,我們可以看到並發現以下事情…

A. 先創建 ContextImpl 物件實例,再創建 Activity 實例

B. 之後透過 ContextImpl#setOuterContext 方法,來連接 Context 與 Activity 的關係(有就是在 Context 內放置 Activity 物件)

graph LR subgraph Context 實例 a1(Activity) end

C. 最後使用 Activity#attach 方法來連接 Application、Context 兩者之間的關係,到了這裡我們就可以看到 Activity 是如何與 Context 產生關係的

graph LR Activity --> |產生關聯| Application Activity --> |產生關聯| Context

更多的物件導向設計

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

設計建模 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?

發表迴響