装饰者解耦的秘诀
- 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是上游程序员两个必备的工具,后续文章继续进行分析