装饰者模式

装饰者模式定义

装饰模式又名包装(Wrapper)模式。是一种在不改变原有对象的基础之上,将功能附加到对象上。提供了比继承更有弹性的替代方案(扩展原有对象功能)。

装饰者模式能够动态地将责任附加到对象身上的一种设计模式,若要扩展功能,装饰者提供了比继承更有弹性的替代方案,比生成子类更加灵活。通常在继承关系中,为了扩展功能需要新增子类进行扩展,而装饰者模式,可以在不扩展子类的情况下,将对象的功能进行动态的扩展。

下面我们通过一个具体的例子,详细介绍一下装饰者模式:

某教育咨询公司“学帮忙”,为了帮助学生更好的了解每个大学信息,打算做一个大学院校综合实力评分系统。系统可以根据每个大学的信息,计算出大学的综合实力得分:

  1. 普通本科记10分,独立学院记8分,中外合办记8分,高职专科学校记5分。

  2. 211和985学校额外分别加20分。

  3. 每一个硕士点加1分。

  4. 每一个博士点加2分。

于是他们针对这种需求,进行了软件的设计和开发。

image.png

设计一个抽象的大学接口,然后每个大学对其进行实现,这样就可以计算出每个大学的的综合实力得分。

但是,当他们开始对系统进行实现时,百度了一下中国有多少所大学。。。

image.png

将近3000所大学,如果按照这个模式进行实现的话,那么将有3000个具体的类实现,很显然,这是不现实的。。。

而且就算我们开始的时候发动人力,对这3000所学校进行了实现,但是如果以后计算规则发生改变,比如一个211院校额外加15分,那么再回过头去修改这3000个学校,将又是很大的一个工程。

那么有没有办法能不生成这么多类呢?聪明的你一定已经想到,可以在子类里面标记大学的具体信息,然后在基类里面计算大学得分。

image.png

结合代码我们看一下

public abstract class University {
    public abstract String getUnvName();
    public abstract int getUnvType();
    public abstract boolean is211();
    public abstract boolean is985();
    public abstract int getMasterNum();
    public abstract int getDoctorNum();
    public int getPoint(){
        int point = 0;
        switch (getUnvType()){
            case 0: //普通本科
                point = 10;
                break;
            case 1: //独立学院
            case 2: //中外合资
                point = 8;
                break;
            case 3: //高职院校
                point = 5;
                break;
        }

        if (is211()){
            point += 20;
        }

        if (is985()){
            point += 20;
        }

        point += getMasterNum();
        point += getDoctorNum() * 2;
        return point;
    }
}

我们结合上面的类图和代码,可以看出,我们如果想得到一个大学的分数,只需要让他继承University类,然后调用getPoint方法即可,但是这种方案就没有弊端了吗?

设想一下几个场景:

  1. 计算规则变了,211院校额外加25分。

  2. 新增了计算规则,对于有强基计划的学校额外加10分。

如果发生这样的需求变更的话,作为开发者的我们都知道,需求是肯定会变更的。。。当需求变更了,我们必须要修改基类了,不是不可以,但是这样做违背了软件设计的开闭原则(类应该对扩展开放,对修改关闭。)

那么怎么才能避免上面这些问题呢?这就需要用到本文介绍的设计模式了,装饰者模式:

在不改变原有对象的基础之上,将功能附加到对象上。提供了比继承更有弹性的替代方案(扩展原有对象功能)

我们先来看一下装饰者模式的类图。

image.png

通过上面的类图我们可以了解装饰者模式的具体结构样式,把这个模式套用到大学评分系统中,可以得到如下的类图。

image.png

在该系统中,四种不同的大学类型,是我们扩展大学基类后获得的不同类型院校,分别是普通本科,独立学院,中外合办还有高职专科。UnvFeature类是装饰者的基类,用来标识大学的综合实力,也就是评分的标准。最下面四个是具体的装饰者,分别用于标识大学是否是211,985以及大学的硕士和博士点个数。

大学装饰者模式的类我们都已经实现好了,那么怎么才能计算出大学的得分呢?

image.png

比如我们新建一个天津大学,然后分别用211,985和硕士博士的装饰者对其进行装饰,这样一来,我们最后得到的一个包裹了很多层,但是依然是University的对象,调用这个University的getPoint方法,那么就可以逐层计算得分,最后返回一个具体得分。

下面我们看一下具体的代码实现:

**
 * 大学基类
 */
public abstract class University {
    public abstract String getUnvName();
    public abstract int getPoint();
}
/**
 * 普通本科
 */
public class NormalUniversity extends University {
    String name;

    public NormalUniversity(String name) {
        this.name = name;
    }

    @Override
    public String getUnvName() {
        return name;
    }

    @Override
    public int getPoint() {
        return 10;
    }
}
/**
 * 大学特征装饰者
 */
public abstract class UnvFeature extends University{
}
/**
 * 211院校
 */
public class Unv211 extends UnvFeature {
    University unv;

    public Unv211(University unv) {
        this.unv = unv;
    }

    @Override
    public String getUnvName() {
        return null;
    }

    @Override
    public int getPoint() {
        return unv.getPoint() + 20;
    }
}
/**
 * 硕士点
 */
public class UnvMaster extends UnvFeature {
    University unv;
    int masterNum;

    public UnvMaster(University unv, int num) {
        this.unv = unv;
        this.masterNum = num;
    }

    @Override
    public String getUnvName() {
        return null;
    }

    @Override
    public int getPoint() {
        return unv.getPoint() + masterNum;
    }
}

这里只列举几个特征类的实现方式,其他类的实现也与此类似,因篇幅问题不在具体列出。

那么我们计算一个大学的得分点,只需要执行如下代码

University tju = new NormalUniversity("天津大学");
tju = new Unv211(tju);
tju = new Unv985(tju);
tju = new UnvMaster(tju, 39);
tju = new UnvDoctor(tju, 29);
int point = tju.getPoint();

到此,我们的大学评分系统设计完成。回过头看一看我们上面的两个问题是否得到解决:

问题1.如果211大学评分标准发生改变,那么我们只需要修改211大学这个装饰者的类即可,不需要修改基类,也不需要变动计算方法。

问题2.如果新增计算标准,那么我们只需要新增一个装饰者即可。比如新增双一流大学可额外加10分,我们可添加如下新的装饰者:

/**
 * 双一流院校
 */
public class UnvDual extends UnvFeature {
    University unv;

    public UnvDual(University unv) {
        this.unv = unv;
    }

    @Override
    public String getUnvName() {
        return null;
    }

    @Override
    public int getPoint() {
        return unv.getPoint() + 10;
    }
}

对此,我们的问题得到圆满解决。

装饰者模式的在实际工作中的运用

在Android开发过程中,我们最常用的四大组件之一就是Activity了,那你知道Activity中所包含的设计模式吗?要搞清楚这一点,我们先来看看Activity相关的类图。

image.png

Context是个抽象类,它的唯一实现是ContextImpl。ContextWrapper通过包装ContextImpl来实现扩展,保证了ContextImpl类最原始的上下文特性。然后通过ContextWrapper的多态实现,来生成系统组件Activity,Service,Application。ContextWrapper是Android系统中一个很重要的类,从名称上不难看出就是使用了装饰者模式。通过ContextWrapper构造方法发现传入了一个Context对象,并赋给mBase变量,ContextWrapper就是包装了mBase。

public class ContextWrapper extends Context {
    Context mBase;

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

    /**
     * Set the base context for this ContextWrapper.  All calls will then be
     * delegated to the base context.  Throws
     * IllegalStateException if a base context has already been set.
     *
     * @param base The new base context for this wrapper.
     */
    protected void attachBaseContext(Context base) {
        if (mBase != null) {
            throw new IllegalStateException("Base context already set");
        }
        mBase = base;
    }
    //......

而Application,Activity和Service都相当于具体的装饰者,他们分别具有自己的特性,同时又对Context进行了包装,因此他们都可以提供各种获取系统环境的方法, 比如getResource, getPackageManager, getContentProvider等。

如果Android系统后期考虑添加新的系统组件,可以通过继承ContextWrapper这个类,并且实现它自己应有的功能,这样不需要修改已经很完善的Activity等类,就可以通过添加新的装饰者实现新的需求。

装饰者模式的优点和缺点

1.继承的有力补充,比继承灵活,不改变原有对象的情况下给一个对象扩展功能。(继承在扩展功能是静态的,必须在编译时就确定好,而使用装饰者可以在运行时决定,装饰者也建立在继承的基础之上的)

2.通过使用不同装饰类以及这些类的排列组合,可以实现不同的效果。

3.符合开闭原则

同样,装饰者模式也是有一定的缺点的

1.会出现更多的代码,更多的类,增加程序的复杂性。

2.动态装饰时,多层装饰时会更复杂。(使用继承来拓展功能会增加类的数量,使用装饰者模式不会像继承那样增加那么多类的数量但是会增加对象的数量,当对象的数量增加到一定的级别时,无疑会大大增加我们代码调试的难度)

比如我们看一下JAVA中的装饰者模式


image.png

当我们第一次接触InputSteam相关内容时,是不是很难理解每一个具体装饰者的作用?只有当我们对每一个具体InputSteam都进行尝试之后,才知道什么时候该用哪个去实现我们所需要的功能。

所以在程序设计中,要根据具体使用场景,选择最适合的设计模式,发扬其优点,尽量避免其缺点的扩大。

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

推荐阅读更多精彩内容