人人都会设计模式---装饰模式--Decorator

2,874 阅读12分钟

装饰模式大纲

PS 转载请注明出处
作者:TigerChain「公号 TigerChain」
地址:juejin.cn/post/684490…
掘金:juejin.cn/user/118712…

教程简介

  • 1、阅读对象 本篇教程适合新手阅读,老手直接略过
  • 2、教程难度 初级,本人水平有限,文章内容难免会出现问题,如果有问题欢迎指出,谢谢
  • 3、Demo 地址
    Java Demo 地址:github.com/githubchen0…
    Android Demo 地址:github.com/githubchen0…Decorator 部分

正文

一、什么是装饰模式

1、生活中的装饰模式

1、穿衣服

这个模式每个人都知道,我们天天穿衣服「有的人天天换衣服」,这就是一个装饰模式,装饰谁呢?当然是自己了。男人让自己变得更加有精气神,女人让自己变得更加靓丽有气质,无形中给人一种附加的"能力"「吸引人」

2、房子装修

我们买的房子要装修了,可以装成高端大气上档次的,也可以装成低调奢华有内涵的,还可以装成简易风格、欧美风格等等,房子还是原来的房子,但是通过装修我们赋予了房子不同的体现风格--这就是装饰模式

3、礼品盒,包装盒

同样的产品不同的包装卖的价格不一样,以苹果为例,一般的苹果一斤斤的卖,礼品盒的苹果按个数卖价格是没有包装的几倍,包装经济下的利润都是非常可观的,其实我们做为程序员也是这个道理,如果自己有开源项目、有高仿问量的博客、有知名度「都是对自己的包装」,那么自己的身价也不低「学完装饰模式赶紧给自己镀金吧」

4、孙悟空炼就火眼金睛

我们都知道猴子被放到八卦炉里炼了七七四十九天,没有想到猴子没有死,反到变成火眼金睛了,这无形之中又用到了装饰模式「猴子还是原来的猴子可是多了一项技能」

2、程序中的装饰模式

装饰模式的定义

在不改变原有对象的基础之下给原有对象扩展功能,是对继承关系的一种替换方案,装饰模式也叫包装器模式「Wrapper Pattern」

装饰模式的目的

定义里说的很清楚了就是扩展原有对象的功能「不改变原有对象」 当然在对原有对象不改变的前提下扩展对象的功能可不止这一种做法:通常的做法会有

  • 1、继承
  • 2、动态代理
  • 3、装饰模式
  • 4、其它等等

动态代理应该算是最好的一种解决方案,但是还有有区别的,具体下面会说

装饰模式的结构

角色 类别 说明
Component 抽象角色「抽象类或接口」 抽象的构件对象,真实对象和装饰对象都实现此相同的接口,不是必须的
ConcreteComponent 具体的构件角色 即被装饰的对象可以有多个「因为被装饰的对象可以有多个」
Decorator 装饰角色 持有一个抽象构件的引用,并且把客户端的请求转发给真实的对象,起到扩展真实对象的功能,不是必须的
ConcreteDecorator 具体的装饰角色 负责给构件对象扩展功能

装饰模式简单的 UML

装饰模式简单的 UML

装饰者类装饰的对象是具体的构件类

二、装饰模式的举例

1、人穿衣服

人靠衣装马靠鞍,说的就是包装对一个人来说非常重要。当然男人有男人的装扮风格,女人就女人的装扮风格,如果是一个明星再穿个个性的衣服一般就会达到炒作的目的--结果可想而知身价倍增,那么对于我们平常人来说,穿着也非常重要,比如你面视的时候,穿着得体映象分就会大大提高,可见装饰模式对我们有多么主要呀

人穿衣服简单的 UML

人搭配衣服简单的 UML

根据 UML 撸码

  • 1、装饰装饰肯定要有装饰的对象,定义一个抽象的被装饰者 Person.java
/**
 * Created by TigerChain
 * 抽象的构件--人
 */
public interface Person {
    // 取得搭配的衣服
    String getCloths() ;
}
  • 2、定义具体的构件--即具体的被装饰者 TigerChain.java
/**
 * Created by TigerChain
 * 具体的构件 -- TigerChain  即被装饰的对象
 */
public class TigerChain implements Person {
    @Override
    public String getCloths() {
        String selectResult = "TigerChain 搭配衣服:" ;
        return selectResult;
    }
}
  • 3、穿一身衣服有上衣、裤子、鞋子等,这里只考虑上衣和裤子「上衣和裤子也是各种样式 和品牌,我们统一抽象成装饰者」,定义抽象装饰者上衣 IOuter.java
/**
 * Created by TigerChain
 * 上衣,抽象的装饰者,被装饰的对象是人
 */
public interface IOuter extends Person{
    String getOuter() ;
}
  • 4、定义具体的上衣装饰者 1 西装 Suit.java
/**
 * Created by TigerChain
 * 上衣具体的装饰者---西服
 */
public class Suit implements IOuter {

    private Person person ;
	// 这里体现出装饰「即包装,把被装饰的对象包起来」
    public Suit(Person person){
        this.person = person ;
    }

    @Override
    public String getCloths() {
        return person.getCloths()+"西装";
    }

    @Override
    public String getOuter() {
        return "---上衣 ";
    }
}
  • 5、定义具体的上衣装饰者 2 夹克 Jacket.java
/**
 * Created by TigerChain
 * 具体的上衣装饰者,皮夹克
 */
public class Jacket implements IOuter {
	// 被包装的抽象类
    private Person person ;

    public Jacket(Person person){
        this.person = person ;
    }

    @Override
    public String getCloths() {
        return person.getCloths()+" 皮夹克";
    }

    @Override
    public String getOuter() {
        return "---上衣";
    }
}
  • 6、定义抽象的装饰者裤子 ITrousers.java
/**
 * Created by TigerChain
 * 抽象的装饰者--裤子
 */
public interface ITrousers {
    String getCloths() ;
}
  • 7、定义具体的裤子装饰者 1 西裤 Pants.java
/**
 * Created by TigerChain
 * 裤子的具体装饰者--西裤
 */
public class Pants implements ITrousers {

    private Person person ;

    public Pants(Person person){
        this.person = person ;
    }

    @Override
    public String getCloths() {
        return person.getCloths()+"西裤";
    }
}
  • 8、定义具体的裤子装饰者 2 牛仔裤 Jean.java
/**
 * Created by TigerChain
 * 具体的裤子装饰者--牛仔裤
 */
public class Jean implements ITrousers {

    private Person person ;
    public Jean(Person person){
        this.person = person ;
    }
    @Override
    public String getCloths() {
        return person.getCloths()+"牛仔裤";
    }
}
  • 9、来个测试类,测试一下 Test.java
/**
 * Created by TigerChain
 * 测试类
 */
public class Test {
    public static void main(String args[]){

        Person person = new TigerChain() ;
        System.out.println("方式一:全身西装");
        // 上衣西装
        IOuter suit = new Suit(person) ;
        System.out.println(suit.getCloths()+suit.getOuter());
        // 裤子西裤
        ITrousers pants = new Pants(person) ;
        System.out.println(pants.getCloths());

        System.out.println("方式二:皮夹克+牛仔裤");
        // 上衣皮夹克
        IOuter jacket = new Jacket(person) ;
        System.out.println(jacket.getCloths()+jacket.getOuter());
        // 裤子牛仔裤
        ITrousers jean = new Jean(person) ;
        System.out.println(jean.getCloths());
    }
}

  • 10、运行查看结果

搭配衣服结果

怎么样,我还是我没有改变,只是换个皮而已,没什么大不了的。从结果来看,我个人还是比较土的「不会搭配衣服 ^_^」

2、装修房子

当初我装修房子的时候--装修公司给我们介绍他们有两种装修风格:简约和欧美风格,问我要什么风格的,作为屌丝的我选择了简约风格「其实不喜欢欧美风格,感觉越简单越好,主要还是手头没有银子」

这里被装饰的对象就是房子、装修风格就是装饰者,我们来看看 UML 吧

装修房子简单的 UML

装修房子简单的 UML

根据 UML 撸码

  • 1、定义抽象的被装饰的对象 AbstractHouse.java
/**
 * Created by TigerChain
 * 抽象的构件房子
 */
public abstract class AbstractHouse {
    // 取得房子的风格
    abstract String getCategory() ;
}
  • 2、定义具体的被装饰对象简约房子 JianYi.java
/**
 * Created by TigerChain
 * 具体的构件1 简易房子
 */
public class JianYi extends AbstractHouse {
    @Override
    String getCategory() {
        return "简易风格的房子";
    }
}
  • 3、定义具体的被装饰对象欧美房子 OuMei.java
/**
 * Created by TigerChain
 * 具体的构件2 欧美风格房子
 */
public class OuMei extends AbstractHouse {
    @Override
    String getCategory() {
        return "欧美风格的房子";
    }
}
  • 4、定义抽象的装饰者 AbstractDecorator.java
/**
 * Created by TigerChain
 * 抽象的装饰者
 */
public abstract class AbstractDecorator extends AbstractHouse{
    // 返回装修价格
    abstract String getCost() ;
}
  • 5、定义简约风格房子具体装饰者 DiDiaoDecorator.java
/**
 * Created by TigerChain
 * 具体的装饰者之一:简易装修
 */
public class DiDiaoDecorator extends AbstractDecorator {

    private AbstractHouse abstractHouse ;

    public DiDiaoDecorator(AbstractHouse abstractHouse){
        this.abstractHouse = abstractHouse ;
    }
    @Override
    String getCategory() {
        return this.abstractHouse.getCategory() + " 低调奢华有内涵";
    }

    @Override
    String getCost() {
        return " 8 万元装修";
    }
}
  • 6、定义欧美风格房子具体装饰者 GaoDuanDecorator.java
/**
 * Created by TigerChain
 * 具体的装饰者--高端装修
 */
public class GaoDuanDecorator extends AbstractDecorator {

    private AbstractHouse abstractHouse ;

    public GaoDuanDecorator(AbstractHouse abstractHouse){
        this.abstractHouse = abstractHouse ;
    }

    @Override
    String getCategory() {
        return abstractHouse.getCategory()+" 高端大气上档次";
    }

    @Override
    String getCost() {
        return " 10 万元装修";
    }
}
  • 7、来测试一下 Test.java
public class Test {
    public static void main(String args[]){
        AbstractHouse jianYiHouse = new JianYi() ;
        AbstractDecorator diDiaoDecorator = new DiDiaoDecorator(jianYiHouse);
        System.out.println(diDiaoDecorator.getCategory()+diDiaoDecorator.getCost());


        AbstractHouse ouMei = new OuMei() ;
        AbstractDecorator gaoDuanDecorator = new GaoDuanDecorator(ouMei) ;
        System.out.print(gaoDuanDecorator.getCategory()+gaoDuanDecorator.getCost());
    }
}
  • 8、运行查看结果

装修房子结果

其实这里的 AbstractHouse 不是必须的,我们可以定义一个 House 类,然后包装它即可「这里为了演示包装器模式的全部角色,所以在实际中可以灵活设计」

3、悬浮的 ExpandableListView

我们都知道 ExpandableListView 是实现分组列表的,它本身是不支持组头悬浮功能的,为了达到这一目的,我们要重写 adapter 功能 ExpandableListView 的滚动功能。实现相关功能的三方类库非常多,我这里选择一个叫 FloatingGroupExpandableListView 的三方库,它使用装饰模式来实现悬浮组头的功能,我们来看看吧,我们直接以 FloatingGroupExpandableListView 的 demo 为例来说

先看看 本Demo 地运行结果

  • 1、没有悬浮的 ExpandableListView

普通的 ExpandableListView

  • 2、带悬浮的 FloatingGroupExpandableListView

FloatingGroupExpandableListView

WrapperExpandableListAdapter 简单的 UML

WrapperExpandableListAdapter 简单的 UML

FloatingGroupExpandableListView 的使用方式

  • 1、声明 XML
<com.diegocarloslima.fgelv.lib.FloatingGroupExpandableListView
android:id="@+id/my_list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
  • 2、在 Activity.onCreate() 或 Fragment.onCreateView() 方法中添加如下代码
FloatingGroupExpandableListView myList = (FloatingGroupExpandableListView) findViewById(R.id.my_list);
// 自己定义的 BaseExpandableListAdapter
BaseExpandableListAdapter myAdapter = new MyAdapter();
//看到了吧,从名字可以看到这是一个包装器,用来包装 ExpandableListAdapter 的
WrapperExpandableListAdapter wrapperAdapter = new WrapperExpandableListAdapter(myAdapter);
// 这里把包装 adapter 传进去就达到了悬浮组头的目的
myList.setAdapter(wrapperAdapter);

怎么样,原来的 BaseExpandableListAdapter 压根不用修改就达到扩展的目的,很爽吧,当然当配合 FloatingGroupExpandableListView「扩展了解ExpandableListView 」 来使用

具体代码

具体的代码我就不贴了,我上传到 github 上了,大家可自行下载去查看,Demo 地址:github.com/githubchen0… 的 wrapperExpandableListAdapter 部分

如果想看 FloatingGroupExpandableListView 的源码,包括 WrapperExpandableListAdapter 是如何实现的,我们可以扒扒它的源码:地址是:github.com/diegocarlos… 建议亲看扒扒这部分源码来学习一下

三、Android 源码中的装饰模式

1、ContextWrapper

Context 我们做 Android 的基本上天天和它打交道,启动 Activity ,发送广播,启动服务等,Content 就用到了包装器模式,那是一个叫做 ContentWrapper 的东东

ContentWrapper 简单的 UML

ContentWrapper 简单的 UML

简单的源码分析

ContextWrapper 部分源码

我们可以看到 ContextWrapper 就是对 Context 的一个包装,没有什么好说的,其实我们看 UML 就知道 ContextWrapper 其实是持有 ContextImpl 一个引用的,由于 ContextWrapper 就上面两个方法来接收 Context 的,所以接收的肯定是 ContextImpl,我们接着看 ContextImpl 是如何被传递到 ContentWrapper 的,我们看看 ActivityThread 部分源码

ActivityThread 部分源码
从图我们可以很清楚的看到从 performLaunchActivity 方法开始如何一步步把 ContextImpl 传递给 mBase 了,所以 Activity 就可以任性的调用 Context 的相关方法了

Why?

为什么 Andorid 系统要把 Content 使用包装器包装一下呢?我们试想一下,Activity、Application、Service 等等都要使用 Context ,假使没有 ContentWrapper ,那么我们要使用 Context 只能是继承自 ContextImpl「或者直接继承 Context」,那么如果 Context 要修改「或者有一个新的 ContextImplB 方式是更好的」,会导致 Activity、Application、Service 都要修改「或重新继承 ContextImplB」,这无疑是恶心的。而有了 ContextWrapper ,假如有一个新的 Context 变种,我们直接将所包装的对象替换掉即可就是这么方便和任性

2、 android.hardware.camera2.utils.Decorator

感兴趣的可以扒扒这部分源码,同样使用的装饰模式

/***
This is an implementation of the 'decorator' design pattern using Java's proxy mechanism.
See also:
newInstance(java.lang.Object,android.hardware.camera2.utils.Decorator.DecoratorListener)
Hide:
**
*/

public class Decorator<T> implements InvocationHandler {

    public interface DecoratorListener {
		void onBeforeInvocation(Method m, Object[] args);
		... 省略若干代码
    }
    ... 省略若干代码
 @SuppressWarnings("unchecked")
    public static<T> T newInstance(T obj, DecoratorListener listener) {
        return (T)java.lang.reflect.Proxy.newProxyInstance(
                obj.getClass().getClassLoader(),
                obj.getClass().getInterfaces(),
                new Decorator<T>(obj, listener));
    }
    ... 省略若干代码
}
        

从注释就可以看出使用的装饰模式

四、装饰模式的分类

1、 透明

具体构件角色、装饰角色的接口和抽象构件的接口完全一致,则称为透明模式,这是一种极端的例子不太常见,由于装饰模式就是为了在不改变接口的前提下---扩展对象,扩展肯定就要对外公开一些自己的方法,所以不可能接口完全一致

2、 半透明

如果装饰角色的接口与抽象构件角色不一致,一般情况下装饰角色扩展一些自己的方法,也就是上面我们普遍的例子

五、装饰模式的优缺点

优点

  • 1、比继承灵活,可以动态的给一个对象扩展功能「而不改变原有的对象」,并且不同的装饰器可以实现不同的效果
  • 2、具体的被装饰者和具体的装饰者可以独立变化「新添加一个实现即可」,原有的代码不用改变,符合开--闭原则

缺点

  • 1、会写更多的代码,生成更多的对象
  • 2、如果是封装的三方类,如果装饰类有多个,那么调用者具体实例化那个不太容易确定「其实文档写清楚,这个就不是问题」

六、装饰模式 VS 适配器模式/动态代理

  • 适配器模式:把一个类的接口变成客户所期望的另一个接口,从而使原本因接口不匹配的而无法运行的两个类一起工作,基本上把原的接口都重写成满足自己的接口了,而装饰模式只是对对象行为功能的增加「不改变接口」

  • 动态代理:是使用代理控制对对象仿问「是控制仿问」

  • 装饰模式:动态的新增或组合对象的行为「是为对象的行为的加强」

七、参考资料

八、总结

PS: 文中的 Android 源码均到自 Android Api 26

  • 1、装饰模式是在不改变原有对象的基础之上对对象功能的扩展
  • 2、装饰模式中抽象构件和抽象装饰都都不是必须的「如果只有一个,还抽象个毛线呀」
  • 3、装饰模式提供了比继承更灵活的扩展类行为的方法
  • 4、当我们需要为已有功能动态的添加新功能时可以考虑装饰模式
  • 5、装饰模式和动态代码/适配器模式相似又不同「适用场景不一样」

到此为止,我们的装饰模式「包装器模式」就介绍完了,伸出你的小手给个赞吧

以后文章会第一时间发在公号,请大家添加博文的公号,扫描添加即可关注 公众号:TigerChain

TigerChain