设计模式-装饰者模式(Decorator)理解和在Android中的应用

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Card361401376/article/details/51222351

介绍

最近写代码没有手感,就看看书找点写代码的灵感。看点高大上的设计模式谈谈自己的理解。我读的是《研磨设计模式》看完之后真正的醍醐灌顶。借用一句话

一本值得反复研读的书

读了设计模式,其实我觉得看Android源码不是一行一行的看,毕竟源码的代码太多,其实很多都是输入检查非空判断异常处理等,如果只是关心的关键的实现步骤抓重点的话,就找到关键实现就好了。这话说得有点绕。我们怎么知道什么是重点。这就个人代码功力还有就是设计模式的理解了。
个人功力就靠平常的开发经验积累思维拓展。但是设计模式就可以看书学习啊。这里强行安利,《研磨设计模式》真的神书看完醍醐灌顶。《Android源码设计模式解析与实战》个人觉得不如前者但是可以作为拓展,因为结合Android源码分析更实际。

Android源码和装饰者模式

我在我的博客上-源码分析-ListView组件addHeaderView()方法的源码解析上分析了ListView的实现addHeadView的源码。当时没有看到装饰者模式。只知道Google在对添加headview上包装了一个类,而不是继承一个类。觉得这样写很好实现代码解耦。后来看完《研磨设计模式》的装饰者模式篇才明白为什么这写。

装饰者模式的定义:

动态地给一个对象添加一些额外的职责。就增加功能来说,装饰者模式比生成子类更灵活。

考虑到实际的代码中。比如我们需要给listview添加一个能够添加头部视图headView的功能。这就是需要动态地添加一些额外的职责。而且为了解耦和不影响现有代码,最好是透明的。所谓透明就是给一个对象增加功能,但是不能让这个对象知道,也就是不能去改动这个对象。
从代码的角度看,给对象增加功能,就是拓展对象功能,就是继承啊。这是网上大多数对RecyclerView添加headview时候的真实考虑和代码逻辑实现。但是不透明。我们看到Android源码ListView的addHeadView实现不是这样的。因为继承是非常不灵活的复用方式。这里就需要看装饰者模式是怎么实现透明添加的。

装饰者模式基本思路

在装饰者模式的实现中,为了能够实现和原来使用被装饰对象的代码无缝衔接,是通过定义一个抽象类,让这个类实现与被装饰对象相同的接口,然后在具体实现类中,转调被装饰的对象,在转调前后添加新功能,这就实现了给被装饰对象增加功能。上面这段话最关键的就是-转调。而能转调的实现是定义统一接口。

UML类图描述

文字描述无力,用UML图说明就清晰了
装饰者模式的UML图

说明:

  • Component:组件对象接口
  • ConcreteComponent:具体的组件对象,实现组件对象接口,通常就是被装饰的原始对象。就对这个对象添加功能。
  • Decorator:所有装饰器的抽象父类,需要定义一个与组件接口一致的接口,内部持有一个Component对象,就是持有一个被装饰的对象。
  • ConreteDecoratorA/ConreteDecoratorB:实际的装饰器对象,实现具体添加功能。

    熟悉UML图的基本就明白了,但是像我这样不熟悉的还是写一点我们熟悉的代码描述吧。

    代码描述

    首先是组件对象接口的定义;

/**
 - Created by LiCola on  2016/04/22  15:58
 - 统一的组价对象接口
 */
public abstract class Component {
    //抽象方法 没有具体操作
    public abstract void operation();
}

实现接口的对象

/**
 * Created by LiCola on  2016/04/22  16:00
 * 具体实现组件对象接口的对象 被装饰的原始对象
 */
public class ConcreteComponent extends Component {

    @Override
    public void operation() {
        //重写方法 做基本的操作  
        System.out.print(" ConcreteComponent operation\n");
    }
}

装饰器的抽象父类

/**
 * Created by LiCola on  2016/04/22  16:04
 * 所有装饰器的抽象父类 持有接口 转发请求
 */
public abstract class Decorator extends Component {

    /**
     * 内部持有的组件接口对象
     */
    protected Component component;

    /**
     * 依赖注入
     * @param component 依赖的对象
     */
    public Decorator(Component component) {
        this.component = component;
    }

    /**
     * 转发请求给组件对象 这里可以做一些附加操作
     */
    @Override
    public void operation() {
        component.operation();
    }
}

某个具体实现例子

/**
 * Created by LiCola on  2016/04/22  16:08
 * 某个具体装饰器实现对象,调用接口方法和具有自己的方法 并能够附加上去
 */
public class ConcreteDecoratorA  extends Decorator{

    /**
     * 依赖注入
     *
     * @param component 依赖的对象
     */
    public ConcreteDecoratorA(Component component) {
        super(component);
    }

    public int operationA(){
        System.out.print(" ConcreteDecoratorA operationA\n");
        return 0;
    }

    @Override
    public void operation() {
        operationA();
        super.operation();
    }
}

以上代码只说明层级关系和逻辑,没有真实的操作。
客户端调用代码示例:

/**
 * Created by LiCola on  2016/04/22  16:21
 */
public class Client {
    public static void main(String[] args){
        Component component=new ConcreteComponent();

        Decorator decorator=new ConcreteDecoratorA(component);

        decorator.operation();
    }
}

输出结果:
ConcreteDecoratorA operationA
ConcreteComponent operation

输出结果只是打印两行字符 但是意义重大。
要知道我们只是调用了Decorator的方法但是得到原来的基本操作ConcreteComponent的方法和新增的ConcreteDecoratorA的方法。就是给已经存在对象新增了功能而且没有使用继承,要知道Java的可是有单继承限制。ConcreteDecoratorA只是一个具体实现,实际上我们可以定义很多的同样继承自Decorator类的子类添加功能方法,然后串联起来。

装饰者模式的功能

装饰者模式实现的是从一个对象外部给对象添加功能,相当于改变了对象的外观,装饰过的对象,从外部系统来看已经不再是原来的对象,而是经过一系列装饰器装饰过的对象。
装饰者模式最大的好处就是灵活,它能够灵活的改变一个的对象的功能,并且是动态组合形式。另外好处就是代码复用,因为每个装饰器是独立的,可以给一个对象多次增加同一个装饰器,也可以同一个装饰器装饰不同对象。
在面向对象设计中:有一条基本规则

尽量使用对象组合,而不是对象继承来扩展和复用功能。

另外说明:

各个装饰器之间最好是完全独立的功能,不要依赖,这样在进行装饰组合的时候,才没有先后调用限制。否则会降低装饰器组合的灵活性。

装饰器模式的退化形式:
当仅仅只是添加一个功能,就没有必要再设计装饰器的抽象父类,直接在装饰器中实现组件接口,然后实现相应的拓展功能。

Android源码中存在的装饰者模式

在Android源码中,存在很多设计模式的实现。当理解了23个设计模式看源码也会透彻得多。因为实际代码有很多限制不会存在基本的设计模式,很多时候都是变形后的设计模式实现。

ListView的addHeadView

比如ListView的添加头部和尾部视图/addHeadView和addFootView功能就是装饰者模式的运用。了解的MVC的都知道其实我们添加视图影响的是adapter的方法调用。实际代码的继承关键非常复杂,我就简略的画出关键UML图
ListView的UML

Adapter:接口对象,方法很多只画一个关键的getView方法
MyBaseAdapter:是我们自动定义类继承BaseAdapter(也就是Adapter省略无关继承)用来绑定ListView显示itemView的类,是具体的组件对象,被装饰的原始对象。
HeaderViewListAdapter:退化形式的装饰器,Decorator抽象父类和子类的集合,统一成实际的装饰器对象,持有Adapter对象。
源码解析在我的另外一篇博客就不详细说明了。

Context的startActivity

Context类中也存在装饰者模式的运用,大家看源码中

  • abstract class Context是抽象类就是装饰者模式的Component:组件对象接口。
  • 而真正的实现是在ContextImpl中完成,它继承自Context抽象类并实现抽象方法。是ConcreteComponent:具体的组件对象,实现组件对象接口,就是被装饰的原始对象。
  • 而Decorator所有装饰器的抽象父类是ContextWrapper。从代码上看是完全和装饰者模式一样的实现。内部持有一个Component对象,就是持有一个被装饰的对象。
public class ContextWrapper extends Context {
    Context mBase;

    public ContextWrapper(Context base) {
        mBase = base;
    }

    //省略代码 只看这个方法
   @Override
    public void startActivity(Intent intent) {
        mBase.startActivity(intent);
    }
  • 最后谁是具体的装饰器对象呢?其实就是我们的Activity、Service和Application这些能够启动Activity的实现类啊。只是我们一般没有重写startActivity()方法由系统调用了我们没有注意到。

借用一张UML图可以很好的说明它们之前的关系:
Context的UML图关系

#总结

  • 设计模式在写代码中经常使用到,并且非常重要。有时只是我们使用了而没有注意到它的存在。
  • 我认为理解了设计模式再来看Android源码才能透过现象看本质,没有理解设计模式,看源码时候会纠结在源码的汪洋大海。理解了23种设计模式并能够区分理解运用,看源码就能从更高的层次理解。
  • 全文是我在读《研磨设计模式》之后结合Android实际代码的写的博客算是自己在学习的总结分析。之后还写一些和设计模式有关的博客。
  • 感谢《研磨设计模式》的作者,读到他的书如醍醐灌顶,给我的编程之路指引了方向,解决了很多实际的问题和开拓的眼界。

最后附上豆瓣地址方便大家找书。UML图的画图工具是Chrome的插件UML Diagram Editor。

展开阅读全文

没有更多推荐了,返回首页