裝飾者解耦的秘訣
- 2020 年 4 月 8 日
- 筆記
裝飾者解耦的秘訣
組合優於繼承原則是個很棒的想法,可以解決繼承的地獄。 然而,幾乎沒有庫、示例代碼或者教程來教你如何在 Android 上實現這原則。 這裡思考一下我們如何站在前人的肩膀上去做。
前言
[譯] 如何創建高度模塊化的 Android 應用裏面講解了裝飾者做組合的問題。更多的是使用方法,我們需要站在他的肩膀上去思考這個問題,並做知識的內化。
1、寫代碼的時候的問題
Android 中構建 UI 的職責通常委派給一個類(比如 Activity、Fragment 或 View/Presenter)。這通常涉及到以下任務:
- 填充 View(xml 布局)
- View 配置(運行時參數、布局管理、適配)
- 數據源連接(DB 或者 數據存儲的監聽/訂閱)
- 加載緩存數據
- 新數據的按需請求分派
- 監聽用戶事件(tap、scroll)然後響應事件
除此之外,Activity 和 Fragment 通常還會委派一些額外的職責:
- App 導航
- Activity 結果處理
- Google Play 服務連接和交互
- 過渡動畫配置
2、Decorator庫的分析
裝飾者模式背後的思想是使職責/功能與基類脫鉤,並且不再從基類繼承。

為了使Decorator模式起作用,構建了四個類組件:
- Decorator class具有空方法。這些方法來自基類。他類似觀察者。是用來擴展以添加功能的類。
- Decorators類具有Decorator的列表/映射/數組,該列表/映射/數組將所有回調和可選回調分派到for循環中的Decorator列表中。
- Decoratored類從基類擴展的裝飾類。上圖中的DecoratedFragment, 它包含並初始化一個Decorators對象,並向其分派其原始回調。
- Instigators類,我們稱他為驅動器,它是裝飾器的特例。它產生一些對象,例如適配器實例,並且不能與另一個發起者同時放置。 這裡需要特殊說明的是這個Instigators接口, 實際上他是interface修飾的接口,他有兩個職能。
- 產生一些對象,用get來定義的的方法 比如,下拉刷新的時候,我們要通過getRequestId來獲取請求的ID。可以看到,下拉刷新(上拉加載更多)的裝飾器和獲取id的裝飾器需要通信。
- 提供一些回調的接口,用on來開頭 比如,一個播放器停止或者播放的時候,播放控制按鈕會顯示或者消失。一個播放器的裝飾器,他提供一些回調,這個回調由他來驅動。如onVideoStartPlay(), onVideoStopPlay()等方法。另外的界面裝飾器會根據這些接口的回調來設置相關內容。 我們看下demo中的例子,在藍圖中有如下接口
public interface RequestInstigator { String getRequestId(); void reload(); void loadMore(); }

我們看到通過註解器生成了下面的Decorated和Decorator。其中, UserRequestInstigator和PhotoRequestInstigator來提供接口的實現;其他地方通過裝飾中心獲取到這個id。注意這裡,不是同時出現,一個場景只有一個Instigator,不同的場景通過添加或者移除裝飾器來控制唯一性。
3、自定義裝飾者
看了這個庫的原理之後,我們先簡單的手寫實現一下上面描述的裝飾者模式。(然而分析之後發現這個庫並不是典型意義上的裝飾者)然後再研究一下自動化該如何做。根據上面的類關係,實際上就是看Decorator、Decorators和Decoratored這三個地方如何構建。
我們來做個隱喻。我們剛租了一間房子,房子是農民房,裏面什麼都沒有,我們住進去的時候必須要給這個房子軟裝修一下。比如添加一個床,加個桌子,加個冰箱,加個熱水器等。
這個和一個App頁面構建一樣,基礎的activity的容器有了,我們需要裝飾這個activity的內容,如actionabar,tab ,list等。
- (1)Decorator 添加功能方法的類,為裝飾者的基類,定義一些功能接口。Decorator .java
public abstract class Decorator { protected AppCompatActivity decorated; protected AppCompatActivityDecorators decorators; protected AppCompatActivity getDecorated() { return decorated; } protected void bind() {/**/} protected void unbind() {/**/} protected void onCreate(@Nullable Bundle savedInstanceState) { } protected void onViewInflated() { } protected void onStart() { } protected void onStop() { } protected void onDestroy() { } }
這裡裝飾器裏面持有了被裝飾者的實例,看樣子並沒有有效的解耦和。
- (2)Decoratored 從基類擴展的被裝飾類。比如我們用AppCompatActivity。新增DecoratedActivity.java
public class DecoratedActivity extends AppCompatActivity { private AppCompatActivityDecorators decorators; public final void bind() { try { decorators.bind(this); } catch(Exception e) { e.printStackTrace(); } } public final void unbind() { decorators.unbind(); } @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); decorators.onCreate(savedInstanceState); } @Override protected void onStart() { super.onStart(); decorators.onStart(); } @Override protected void onStop() { super.onStop(); decorators.onStop(); } @Override protected void onDestroy() { super.onDestroy(); decorators.onDestroy(); } }
- (3)Decorators類。具有Decorator的列表/映射/數組,該列表/映射/數組將所有回調和可選回調分派到for循環中的Decorator列表中。 AppCompatActivityDecorators.java
public class AppCompatActivityDecorators { protected final ArrayList<Decorator> decorators; protected final int size; public AppCompatActivityDecorators(){ decorators = new ArrayList<>(); addAllDecorators(); size = decorators.size(); } //這裡先添加幾個簡單的裝飾者來測試 private void addAllDecorators(){ Decorator listDecorator = new ListDecorator(); Decorator titleDecorator = new TitlebarDecorator(); decorators.add(listDecorator); decorators.add(titleDecorator); } public final void onCreate(@Nullable Bundle savedInstanceState) { for (int i = 0; i < size; i++) { decorators.get(i).onCreate(savedInstanceState); } } public final void onViewInflated() { for (int i = 0; i < size; i++) { decorators.get(i).onViewInflated(); } } public final void onStart() { for (int i = 0; i < size; i++) { decorators.get(i).onStart(); } } public final void onStop() { for (int i = 0; i < size; i++) { decorators.get(i).onStop(); } } public final void onDestroy() { for (int i = 0; i < size; i++) { decorators.get(i).onDestroy(); } } }
- (4)業務類的寫法
public class MiniTestActivity extends DecoratedActivity{ @Override protected void onCreate(@Nullable Bundle savedInstanceState) { bind(); super.onCreate(savedInstanceState); setContentView(R.layout.mini_test_main); onViewInflated(); } @Override protected void onDestroy() { super.onDestroy(); unbind(); } }
裝飾者的基礎類加好了,我們就可以加一些業務代碼了。 加一個Titlebar的業務類TitlebarDecorator.java
public class TitlebarDecorator extends Decorator{ private Toolbar toolbar; @Override protected void onViewInflated() { toolbar = (Toolbar) getDecorated().findViewById(R.id.toolBar); toolbar.setTitle("TitleBarDecorator set"); toolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { getDecorated().finish(); } }); } @Override protected void onResume() { super.onResume(); if(toolbar != null) { toolbar.setTitle("TitleBarDecorator onResume"); } } }
- (5)Instigators驅動器接口。上面說了這種接口的兩種職責,一個是提供對象給上層或者其他裝飾器用,一種是提供回調或者設置接口。 我們加一個整體布局獲取的接口:
//Decotator.java中加入 public interface InstigateGetLayoutId { @LayoutRes int getLayoutId(); }
添加新的接口驅動的的裝飾者:
//MiniLayoutInstigator .java public class MiniLayoutInstigator extends Decorator implements Decorator.InstigateGetLayoutId { @Override public int getLayoutId() { return R.layout.mini_test_main; } }
修改Decorators的寫法,
//AppCompatActivityDecorators.java protected final ArrayList<Decorator> decorators; //保存驅動類 private final HashMap<Class, Decorator> noComposeMap; //Decorator 的列表,新加的在裏面加 private static final Class[] COMMON_DECOTATOR = { ListDecorator.class, TitlebarDecorator.class, MiniLayoutInstigator.class }; //接口類的常量列表 private static final Class[] NON_COMPOSABLE = { Decorator.InstigateGetLayoutId.class, }; //獲取列表的方法 protected final Class[] getNonComposable() { return NON_COMPOSABLE; } public AppCompatActivityDecorators() throws InstantiationException, IllegalAccessException{ decorators = new ArrayList<>(); size = COMMON_DECOTATOR.length; Class[] nonComposable = getNonComposable(); noComposeMap = new HashMap<>(nonComposable.length); for (int i = 0; i < size; i++) { Class<? extends Decorator> klass = COMMON_DECOTATOR[i]; //初始化裝飾器 Decorator decorator = klass.newInstance(); //填充接口類到具體的裝飾驅動器的映射表 composableCheck(nonComposable, decorator); decorators.add(decorator); } } private void composableCheck(Class[] nonComposable, Decorator decorator) { for (int i = 0, size = nonComposable.length; i < size; i++) { Class clazz = nonComposable[i]; if (clazz.isAssignableFrom(decorator.getClass())) { // we've found a non composeable class, let's see if it was added ever before if (noComposeMap.get(clazz) != null) { throw new IllegalStateException("This type decorator was already added: " + clazz.getCanonicalName()); } else { noComposeMap.put(clazz, decorator); } } } } //添加相關的接口 protected <I> I getInstigator(Class klass) { return (I) noComposeMap.get(klass); } @LayoutRes public final int getLayoutId() { Decorator.InstigateGetLayoutId deco = getInstigator(Decorator.InstigateGetLayoutId.class); if (deco != null) { return deco.getLayoutId(); } else { throw new DecoratorNotFoundException(); } }
DecoratedActivity.java類添加相應的接口
@LayoutRes public int getLayoutId() { try { return decorators.getLayoutId(); } catch(DecoratorNotFoundException decoratorNotFoundException) { //region user inputed code return R.layout.mini_test_main; //endregion } }
最後在MiniTestActivity.java使用的地方
int mainId = getLayoutId(); setContentView(mainId);
後面可以加更多的裝飾者驅動器,來提供接口給其他的裝飾器用。 更多的例子
總結一下,這裡裝飾者實際上跟真正的裝飾者設計模式還是有很大的區別,首先bind的時候裝飾者中耦合了被裝飾者,而設計模式中的裝飾器只是依賴接口。所以這裡的裝飾者,更像是LifeCycle。
4、設計模式中的裝飾者模式
裝飾者模式指的是在不必改變原類文件和使用繼承的情況下,動態地擴展一個對象的功能。它是通過創建一個包裝對象,也就是裝飾者來包裹真實的對象。
所以裝飾者可以動態地將責任附加到對象上。若要擴展功能,裝飾者提供了比繼承更有彈性的方案。
4.1 裝飾者模式組成結構
- 抽象構件 (Component):給出抽象接口或抽象類,以規範準備接收附加功能的對象。
- 具體構件 (ConcreteComponent):定義將要接收附加功能的類。
- 抽象裝飾 (Decorator):裝飾者共同要實現的接口,也可以是抽象類。
- 具體裝飾 (ConcreteDecorator):持有一個 Component 對象,負責給構件對象「貼上」附加的功能。
4.2 裝飾者模式 UML 圖解
裝飾者模式
例子如下 Java 設計模式之裝飾者模式

可以看到,被裝飾者和裝飾器通過功能的抽象Compoent來解開耦合,顯然這裡的裝飾者有很大區別。叫非典型裝飾工廠更合理一點。
5、自動化和註解
實現了上面的基礎類,這個裝飾器的主要思想已經實現完成。在此基礎上進一步提高開發效率,防止出錯。我們需要更加智能的生成類的方式。這個時候註解就出場了,Android Annotation 和Java Poet是上遊程序員兩個必備的工具,後續文章繼續進行分析