装饰者解耦的秘诀

装饰者解耦的秘诀

组合优于继承原则是个很棒的想法,可以解决继承的地狱。
然而,几乎没有库、示例代码或者教程来教你如何在 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是上游程序员两个必备的工具,后续文章继续进行分析

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,881评论 4 368
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,052评论 1 301
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,598评论 0 250
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,407评论 0 217
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,823评论 3 294
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,872评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,037评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,778评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,505评论 1 247
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,745评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,233评论 1 264
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,568评论 3 260
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,231评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,141评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,939评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,954评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,784评论 2 275

推荐阅读更多精彩内容