裝飾者解耦的秘訣

裝飾者解耦的秘訣

組合優於繼承原則是個很棒的想法,可以解決繼承的地獄。 然而,幾乎沒有庫、示例代碼或者教程來教你如何在 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();  }

demo解析

我們看到通過註解器生成了下面的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是上遊程序員兩個必備的工具,後續文章繼續進行分析