--- title: 'Decorate 裝飾模式' disqus: kyleAlien --- Decorate 裝飾模式 === ## Overview of Content 如有引用參考請詳註出處,感謝 :smile: > 裝飾模式,在不改變其繼承類別動態拓展功能的方法,**==繼承的替代方案之一==**,**裝飾模式比繼承生成子類更靈活** 它的實現可參考 Java's IO 流、Android 的第三方庫 RxJava :::success * 如果喜歡讀更好看一點的網頁版本,可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/) 本篇文章對應的是 [**Decorate 裝飾模式 | 解說實現 | Android Framework Context**](https://devtechascendancy.com/object-oriented_design_decorate/) ::: [TOC] ## Decorate 使用場景 1. 當不能使用繼承或是使用了繼承後不利於維護時可以使用 :::warning 當繼承超過 2 層的時候,其實程式會難以維護,這時其實就可以重新檢視程式,並考慮轉換設計 ::: 2. 以不影響其他類別的方式 **動態的使用(動態新增、移除功能)、撤銷其類別,而不必依靠覆寫** (Override) :::success 繼承是靜態的給類新增功能、裝飾則是動態的給類新增功能 ::: 3. 對於原先的類做加裝、改裝功能(並不影響原先的類) ### Decorate 定義 & Decorate UML * **Decorate 定義** **動態的給一個物件添加(減少)額外功能,裝飾模式相比繼承更加靈活** * **Decorate 角色介紹** | 角色 | 功能說明 | | -------- | -------- | | **Component** | 可以是一個抽象,或是接口,它是最原始的物件(有最基礎、最核心的方法) | | **ConcreateComponent** | 具體實現類,也是等待被裝飾(被代理)者,它實現了最基礎的方法 | | **Decorator** | **它^1.^ 繼承 Component 類 並 ^2.^ 依賴抽象(Component 類)取代繼承**,內部持有一個抽象 Component 物件 | | **ConcreateDecorator** | 具體裝飾者,你要它組裝結合 ConcreateComponent 的實做一起表現出來 | > **依靠`注入抽象模組`(高層模組),使用別類(低層模組)實現功能** > ![](https://hackmd.io/_uploads/rJt6WgYSh.png) ### Decorate 設計 - 優缺點 * **Decorate 設計優點** : 1. 可以**減少里式原則帶來的,繼承時帶來的負荷** (一定要複寫該類 or 類別方法增多),子類無限擴張等問題 > 當然你需要切細每個類的方法 2. 用戶可依據需求使用,而不改變該類的方法,符合開閉原則 :::info 子類可以單獨發展,而不必受到繼承約束 ::: 3. 裝飾、被裝飾類都可以獨立發展!不會相互依賴~ (裝飾類依賴的是抽象) * **Decorate 設計缺點** : 1. 高層模組發生變化時勢必會影響到繼承的所有子類別 2. Debug 會更困難,因為**層層嵌套,邏輯變複雜** (可讀性降低) 3. 多層裝飾會導致效率變差 (仍是層層傳遞的問題) ## Decorate 實現 ### Decorate 標準實現 1. **`Component` 類**:IWear 擁有最基礎、核心的方法抽象 ```kotlin= interface IWear { fun wearClothes() } ``` 2. **`ConcreateComponent` 類**:待被裝飾的類(也可以說待被代理) ```kotlin= class NoWorkPerson : IWear { override fun wearClothes() { println("Wear Underwear") } } ``` 3. **`Decorator` 類**:繼承於 `IWear` 類,並持有一個 `IWear` 物件 ```kotlin= abstract class DecorClothes constructor(val realWear: IWear) : IWear { override fun wearClothes() { realWear.wearClothes() } } ``` 4. **`ConcreateDecorator` 類**:這個類的重點是在於實現你自己的邏輯,你要如何裝飾傳入的實際類 (這裡創建兩個裝飾類) ```kotlin= 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() } } ``` * 作為使用者方,使用裝飾模式: ```kotlin= fun main() { // 一層一層裝飾,最終返回最後一個裝飾者 val finalMan = NoWorkPerson().let { noWorkPerson -> WorkPerson(noWorkPerson) }.let { workPerson -> WorkHardPerson(workPerson) } // 其實最終 finalMan 類型是 WorkHardPerson finalMan.wearClothes() } ``` > ![](https://i.imgur.com/31FdiiQ.png) ## Android Source Context 分析 Context 物件以說是 Android 開發者最熟悉,也是最不熟悉的物件了,它應用於 Android 4 大組件中(`Activity`、`Service`、`ContentProvider`、`Broadcast`),而與 Context 最直接關聯、也最常見的就是 Activity 跟 Application 而 Context 本身是個抽象類,在使用時其實也用到了裝飾模式 ```java= // Context.java public abstract class Context { ... 省略 } ``` :::success 以下分析的是 [**Android 10**](https://cs.android.com/android/platform/superproject/+/android-10.0.0_r40:) 的源碼 ::: ### Context & ContextImpl 的裝飾角色 * 我們這邊先直接揭露 [**Context**](https://cs.android.com/android/platform/superproject/+/android-10.0.0_r40:frameworks/base/core/java/android/content/Context.java) & [**ContextImpl**](https://cs.android.com/android/platform/superproject/+/android-10.0.0_r40:frameworks/base/core/java/android/app/ContextImpl.java) 在裝飾模式下的角色,再進行分析 | 裝飾模式 Prototype 角色 | Android 實做類 | 功能說明 | | ------- | - | -------- | | **Component** | `Context` | 可以是一個抽象,或是接口,它是最原始的物件(有最基礎、最核心的方法) | | **ConcreateComponent** | `ContextImpl` | 具體實現類,也是等待被裝飾(被代理)者,它實現了最基礎的方法 | | **Decorator** | ``ContextImpl``、`ContextWrapper` | **它^1.^ 繼承 Component 類 並 ^2.^ 依賴抽象(Component 類)取代繼承**,內部持有一個抽象 Component 物件 | Android Context 實現裝置模式的 UML 概念圖如下 > ![image](https://hackmd.io/_uploads/S1AFBbp0a.png) ### Context & ContextImpl 裝飾結構分析 * Context 類作為裝飾模式中抽象的「原始物件」,它並不實做,但擁有許多核心的抽象方法,其中就包括 **資源管理**、**文件管理**、**包管理**、**類加載**、**權限管理**、**提供系統服務**… 等等功能,我們依照 4 大組件的方向去看看 Context 與其的重要性 1. **裝飾模式下 Context 是作為 `Component` 角色** * 最常見 Context 提供「**Activity**」的相關功能如下 ```java= // 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**」的相關功能如下 ```java= // 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**」的相關功能如下 ```java= // 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**」的相關功能如下 ```java= // Context.java public abstract class Context { ... 省略 // 為該應用取得一個 ContentResolver 物件 public abstract ContentResolver getContentResolver(); } ``` * 最常見 Context 提供的重要功能: ```java= // 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); } ``` 2. **裝飾模式下 ContextImpl 類,既可作為作為 `ConcreateComponent` 也可作為 `Decorator` 角色**,也就是它本身可以是個沒有裝飾功能的「實做」,也可以「裝飾類」 以下是 [**ContextImpl 的建構函數**](https://cs.android.com/android/platform/superproject/+/android-10.0.0_r40:frameworks/base/core/java/android/app/ContextImpl.java;l=2516):我們可以看到,它是可以傳入 `ContextImpl` 類,而這個參數其實也就透漏個它是一個裝飾設計(也就是說可以加強、減弱、使用… 傳入的 ContextImpl 物件) ```java= // 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**) ```java= // 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) ```java= // 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` 類**](https://cs.android.com/android/platform/superproject/+/android-10.0.0_r40:frameworks/base/core/java/android/content/ContextWrapper.java),從這個類中我們可以很清晰的看到 ContextWrapper 其實沒做甚麽事情,單純的是把外部傳入的 Context 保存起來,並在需要的時候呼叫使用 我們看看最常使用的 `startActivity` 類就可以發現,源碼如下 ```java= // 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` 函數: :::warning * 這裡我們忽略 Android 系統是如何啟動的細節,直接看創建 Activity 的關鍵代碼,也就是從 [**ActivityThread**](https://cs.android.com/android/platform/superproject/+/android-10.0.0_r40:frameworks/base/core/java/android/app/ActivityThread.java;l=3347)#`performLaunchActivity` 開始看 ::: ```java= // 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 } ``` 可以主要的相關源碼中,我們可以看到並發現以下事情… 1. 先創建 `ContextImpl` 物件實例,再創建 `Activity` 實例 2. 之後透過 `ContextImpl`#`setOuterContext` 方法,來連接 Context 與 Activity 的關係(有就是在 Context 內放置 Activity 物件) ```mermaid graph LR subgraph Context 實例 a1(Activity) end ``` 3. **最後使用 `Activity`#`attach` 方法來連接 Application、Context 兩者之間的關係**,到了這裡我們就可以看到 Activity 是如何與 Context 產生關係的 ```mermaid graph LR Activity --> |產生關聯| Application Activity --> |產生關聯| Context ``` ## 更多的物件導向設計 物件導向的設計基礎如下,如果是初學者或是不熟悉的各位,建議可以從這些基礎開始認識,打好基底才能走個更穩(在學習的時候也需要不斷回頭看)! :::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 設計模式` `基礎進階`