java/android 设计模式学习笔记(7):装饰者模式

这篇将会介绍装饰者模式(Decorator Pattern),装饰者模式也称为包装模式(Wrapper Pattern),结构型模式之一,其使用一种对客户端透明的方式来动态的扩展对象的功能,同时它也是继承关系的一种替代方案之一,但比继承更加灵活。在现实生活中也可以看到很多装饰者模式的例子,或者可以大胆的说装饰者模式无处不在,就拿一件东西来说,可以给它披上无数层不一样的外壳,但是这件东西还是这件东西,外壳不过是用来扩展这个东西的功能而已,这就是装饰者模式,装饰者的这个角色也许各不相同但是被装饰的对象本质是不变的。
我们的目标是允许类统一扩展,在不修改现有代码的情况下,就可搭配新的行为。如能实现这样的目标,有什么好处呢?这样的设计具有弹性,可以应对改变,可以接受新的功能来应对改变的需求,也就是 OO 原则中的对扩展开放和对修改关闭的开闭原则。
PS:对技术感兴趣的同鞋加群544645972一起交流

设计模式总目录

java/android 设计模式学习笔记目录

特点

动态地给一个对象添加一些额外的职责,就增加功能来说,装饰者模式相比生成子类更加灵活,提供了有别于继承的另一种选择。
装饰者模式可以静态的,或者根据需要可以动态的在运行时为一个对象扩展功能。被装饰者和众多的装饰者都是继承自一个接口,他们有着一样的行为特性。装饰者模式是继承的另一种选择方式,继承是在编译的时候为类添加新的行为,并且这个改变会影响所有原来该类的实体,装饰者模式就不一样,它提供一种能够在运行时根据需要选择不同运行对象的功能。装饰者模式和继承这两种方式的不同之处在某些扩展功能的情况下显得尤为重要,在一些面向对象编程的语言中,类无法在运行时被创建,而且当需要扩张功能时,这些行为往往无法预测,这就意味着在每个可能的情况下,这个类都需要被创建,所以对比之下,装饰者模式优点在于每个装饰者都是对象,在运行时被创建,并且能够在每次使用时根据需要自己组合。

UML类图

我们现在来看看装饰者模式的 uml 类图:
这里写图片描述
装饰者模式共有四大角色:

    • Component:抽象组件

可以是一个接口或者是抽象类,其充当的就是被装饰的原始对象,用来定义装饰者和被装饰者的基本行为。

    • ConcreteComponent:组件具体实现类

该类是 Component 类的基本实现,也是我们装饰的具体对象。

    • Decorator:抽象装饰者

装饰组件对象,其内部一定要有一个指向组件对象的引用。在大多数情况下,该类为抽象类,需要根据不同的装饰逻辑实现不同的具体子类。当然,如果是装饰逻辑单一,只有一个的情况下我们可以忽略该类直接作为具体的装饰者。

    • ConcreteDecoratorA 和 ConcreteDecoratorB:装饰者具体实现类

对抽象装饰者的具体实现。

在已有的 Component 和 ConcreteComponent 体系下,实现装饰者模式来扩展原有系统的功能,可以分为 5 个步骤

  1. 继承或者实现 Component 组件,生成一个 Decorator 装饰者抽象类;
  2. 在生成的这个 Decorator 装饰者类中,增加一个 Component 的私有成员对象;
  3. 将 ConcreteComponent 或者其他需要被装饰的对象传入 Decorator 类中并赋值给上一步的 Component 对象;
  4. 在装饰者 Decorator 类中,将所有的操作都替换成该 Component 对象的对应操作;
  5. 在 ConcreteDecorator 类中,根据需要对应覆盖需要重写的方法。

装饰者模式在源码中用的也是非常多的,在 Java 和 Android 中都能够见到装饰者模式的影子:

Java 中的装饰者模式

最典型的就是 Java 中的 java.io 包下面的 InputStream 和 OutputStream 相关类了,初学 Java 的时候,看到这些类,头都大了,其实学了装饰者模式之后,再理解这些类就很简单了,画一个简单的类图来表示:
这里写图片描述
InputStream 类是一个抽象组件, FileInputStream,StringBufferInputStream 和 ByteArrayInputStream 类都是可以被装饰者包起来的具体组件;FilterInputStream 是一个抽象装饰者,所以它的四个子类都是一个个装饰者了。

Android 中的装饰者模式

其次,对于 android 开发工程师来说,最最重要的就应该是“上帝类” Context 和其子类了,这些类我就不用解释了,上一张类图基本就明确了:
这里写图片描述
所以对于 Application,Activity 和 Service 等类来说,他们只是一个个装饰者,都是用来装饰 ContextImpl 这个被装饰者类,Application 是在 createBaseContextForActivity 方法中,通过 ContextImpl 的静态方法 createActivityContext 获得一个 ContextImpl 的实例对象,并通过 setOuterContext 方法将两者建立关联;Activity 是通过 handleLaunchActivity 方法设置的 ContextImpl 实例,这个方法会获取到一个Activity对象,在performLaunchActivity函数中会调用该activity的attach方法,这个方法把一个ContextImpl对象attach到了Activity中,具体可以看看我的这篇博客,里面详细介绍到了 Activity 的启动过程:android 不能在子线程中更新ui的讨论和分析。别的类在这里就不介绍了,具体的大家可以去网上查阅相关资料。

示例与源码

我们这里以一个图形系统中的 Window 为例,一般情况窗口都是能够垂直或者是左右欢动的,所以为了能够更好的支持 Window 的滑动,给滑动的 Window 加上一个 ScrollBar 是一个不错的方法,为了重用代码,水平滑动的 Window 和垂直滑动的 Window 我们就能够使用装饰者模式去处理,基本类图如下所示:
这里写图片描述
根据类图,我们首先实现 Window 这个接口:
IWindow.class

public interface IWindow {

    void draw();

    String getDescription();
}

然后是被装饰者 SimpleWindow 类,它实现了窗口的基本行为:
SimpleWindow.class

public class SimpleWindow implements IWindow {
    @Override
    public void draw() {
        Log.e("shawn", "drawing a window");
    }

    @Override
    public String getDescription() {
        return "a window";
    }
}

然后是装饰者类角色的抽象父类:
WindowDecorator.class

public abstract class WindowDecorator implements IWindow{

    private IWindow window;

    public WindowDecorator(IWindow window) {
        this.window = window;
    }

    @Override
    public void draw() {
        window.draw();
    }

    @Override
    public String getDescription() {
        return window.getDescription();
    }
}

最后是实现该装饰者父类的装饰者子类:
HorizontalScrollBarDecorator.class

public class HorizontalScrollBarDecorator extends WindowDecorator {

    public HorizontalScrollBarDecorator(IWindow window) {
        super(window);
    }

    @Override
    public void draw() {
        super.draw();
        Log.e("shawn", "then drawing the horizontal scroll bar");
    }

    @Override
    public String getDescription() {
        return super.getDescription() + " with horizontal scroll bar";
    }
}

VerticalScrollBarDecorator.class

public class VerticalScrollBarDecorator extends WindowDecorator {

    public VerticalScrollBarDecorator(IWindow window) {
        super(window);
    }

    @Override
    public void draw() {
        super.draw();
        Log.e("shawn", "then drawing the vertical scroll bar");
    }

    @Override
    public String getDescription() {
        return super.getDescription() + " with vertical scroll bar";
    }
}

最后测试代码:

switch (v.getId()) {
    case R.id.btn_horizontal_window:
        IWindow horizontalWindow = new HorizontalScrollBarDecorator(new SimpleWindow());
        horizontalWindow.draw();
        Log.e("shawn", "window description : " + horizontalWindow.getDescription());
        break;
    case R.id.btn_vertical_window:
        IWindow verticalWindow = new VerticalScrollBarDecorator(new SimpleWindow());
        verticalWindow.draw();
        Log.e("shawn", "window description : " + verticalWindow.getDescription());
        break;
}

结果:

com.android.decoratorpattern E/shawn: drawing a window
com.android.decoratorpattern E/shawn: then drawing the horizontal scroll bar
com.android.decoratorpattern E/shawn: window description : a window with horizontal scroll bar
com.android.decoratorpattern E/shawn: drawing a window
com.android.decoratorpattern E/shawn: then drawing the vertical scroll bar
com.android.decoratorpattern E/shawn: window description : a window with vertical scroll bar

代码一目了然,结构清晰。
其实说到底,每一个写过 Android 程序的人都应该用过装饰者模式,因为每写一个 Activity,就相当于是写了一个装饰者类,不经意间就用了装饰者模式,大家想一想是不是,哈哈~~

总结

装饰者模式和代理模式有点类似,很多时候需要仔细辨别,容易混淆,倒不是说会把代理模式看成装饰者模式,而是会把装饰者模式看作代理模式。区分一下,装饰者模式的目的是透明地为客户端对象扩展功能,是继承关系的一种替代方案,而代理模式则是给一个对象提供一个代理对象,并由代理对象来控制对原有对象的引用。装饰者模式应该为所装饰的对象增强功能;代理模式对代理的对象施加控制,但不对对象本身的功能进行增强。
同时有几个要点需要提一下:

  • 继承属于扩展形式之一,但不一定是达到弹性设计的最佳方案;
  • 在我们的设计,应该尽量对修改关闭,对扩展开发,无需修改现有代码;
  • 组合和委托可用于在运行时动态加上新的行为;
  • 装饰者可以在被装饰者行为的前后根据实际情况加上自己的行为,必要时也可以将被装饰者行为给替换掉;
  • 可以用无数个装饰者包装一个组件,也就是说,装饰者 A 包装了被装饰者 B ,装饰者 C 再包装装饰者 A,根据实际情况这种行为可以累加到多层,通俗讲就是套上多层外壳;
  • 同时,被装饰者也可以存在多个,也就是说 ConcreteComponent 这个角色也可以是多个的。

装饰者模式的优点就是它的特点:可以在运行时动态,透明的为一个组件扩展功能,比继承更加灵活;缺点也很明显:它会导致设计中出现许多小对象,如果过度使用,会让程序变得很复杂。

适配器 VS 装饰者 VS 桥接 VS 代理 VS 外观

这几个都是结构型设计模式,他们有些类似,在实际使用过程中也容易搞混,我们在这就给他们做一个对比:

适配器模式

适配器模式和其他三个设计模式一般不容易搞混,它的作用是将原来不兼容的两个类融合在一起,uml 图也和其他的差别很大。
uml 类图:
这里写图片描述

装饰者模式

装饰者模式结构上类似于代理模式,但是和代理模式的目的是不一样的,装饰者是用来动态地给一个对象添加一些额外的职责,装饰者模式为对象加上行为,而代理则是控制访问
uml 类图:
这里写图片描述

桥接模式

桥接模式的目的是为了将抽象部分与实现部分分离,使他们都可以独立地进行变化,所以说他们两个部分是独立的,没有实现自同一个接口,这是桥接模式与代理模式,装饰者模式的区别
uml 类图:
这里写图片描述

代理模式

代理模式为另一个对象提供代表,以便控制客户对对象的访问,管理的方式有很多种,比如远程代理和虚拟代理等,这个在上面有,这里就不说了,而装饰者模式则是为了扩展对象。
uml 类图:
这里写图片描述

外观模式

外观模式提供一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。
适配器模式将一个或多个类接口变成客户端所期望的一个接口,虽然大多数资料所采用的例子中适配器只适配一个类,但是你可以适配许多类来提供一个接口让客户端访问;类似的,外观模式 也可以只针对一个拥有复杂接口的类提供简化的接口,两种模式的差异,不在于他们“包装”了几个类,而是在于它们的意图。适配器模式 的意图是,“改变”接口符合客户的期望;而外观模式的意图是,提供子系统的一个简化接口。
uml类图:
这里写图片描述

源码下载

https://github.com/zhaozepeng/Design-Patterns/tree/master/DecoratorPattern

引用

https://en.wikipedia.org/wiki/Decorator_pattern
http://blog.csdn.net/jason0539/article/details/22713711

本系列:



可能感兴趣的文章

发表评论

Comment form

(*) 表示必填项

还没有评论。

跳到底部
返回顶部