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 | 具體實現類,也是等待被裝飾(被代理)者,它實現了最基礎的方法 |
Decorator | 它1. 繼承 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 大組件中(Activity
、Service
、ContentProvider
、Broadcast
),而與 Context 最直接關聯、也最常見的就是 Activity 跟 Application
而 Context 本身是個抽象類,在使用時其實也用到了裝飾模式
// Context.java
public abstract class Context {
... 省略
}
以下分析的是 Android 10 的源碼
Context & ContextImpl 的裝飾角色
● 我們這邊先直接揭露 Context & ContextImpl 在裝飾模式下的角色,再進行分析
裝飾模式 Prototype 角色 | Android 實做類 | 功能說明 |
---|---|---|
Component | Context | 可以是一個抽象,或是接口,它是最原始的物件(有最基礎、最核心的方法) |
ConcreateComponent | ContextImpl | 具體實現類,也是等待被裝飾(被代理)者,它實現了最基礎的方法 |
Decorator | ContextImpl 、ContextWrapper | 它1. 繼承 Component 類 並 2. 依賴抽象(Component 類)取代繼承,內部持有一個抽象 Component 物件 |
Android Context 實現裝置模式的 UML 概念圖如下
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 物件)
C. 最後使用 Activity
#attach
方法來連接 Application、Context 兩者之間的關係,到了這裡我們就可以看到 Activity 是如何與 Context 產生關係的
更多的物件導向設計
物件導向的設計基礎如下,如果是初學者或是不熟悉的各位,建議可以從這些基礎開始認識,打好基底才能走個更穩(在學習的時候也需要不斷回頭看)!
創建模式 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 迭代設計 | 解說實現 | 物件導向設計