您的设计模式——装饰模式【Decorator Pattern 】

Ladies and gentlemen,May I get your attention,Please?,Now I’m going to talk about decorator
pattern.装饰模式在中国使用的那实在是多,中国的文化是中庸文化,说话或做事情都不能太直接,需要
有技巧的,比如说话吧,你要批评一个人,你不能一上来就说你这个做的不对,那个做的不对,你要先肯
定他的成绩,表扬一下优点,然后再指出瑕疵,指出错误的地方,最后再来个激励,你修改了这些缺点后
有那些好处,比如你能带更多的小兵,到个小头目等等,否则你一上来就是一顿批评,你瞅瞅看,肯定是
不服气,顶撞甚至是直接“此处不养爷,自有养爷处”开溜哇。这是说话,那做事情也有很多,在山寨产
品流行之前,假货很是比较盛行的,我在 2002 年买了个手机,当时老板吹的是天花乱坠,承诺这个手机是
最新的,我看着也像,壳子是崭新的,包装是崭新的,没有任何瑕疵,就是比正品便宜了一大截,然后我
买了,缺钱哪,用来 3 个月,坏了,一送修,检查,说这是个新壳装旧机,我晕!拿一个旧手机的线路板,
找个新的外壳、屏幕、包装就成了新手机,装饰模式害人不浅呀!
我们不说不开心的事情,今天举一个什么例子呢?就说说我上小学的的糗事吧。我上小学的时候学习
成绩非常的差,班级上 40 多个同学,我基本上都是在排名 45 名以后,按照老师给我的定义就是“不是读
书的料” ,但是我老爸管的很严格,明知道我不是这块料,还是往赶鸭子上架,每次考试完毕我都是战战兢
兢的, “竹笋炒肉”是肯定少不了的,能少点就少点吧,肉可是自己的呀。四年级期末考试考完,学校出来
个很损的招儿(这招儿现在很流行的) ,打印出成绩单,要家长签字,然后才能上五年级,我那个恐惧呀,
不过也就是几秒钟的时间,玩起来什么都忘记了。
我们先看看这个成绩单的类图:

这里写图片描述

成绩单的抽象类,然后有一个四年级的成绩单实现类,先看抽象类:

package com.alex.patterns.decorator;

/**
 * @author Alex
 * 成绩单的抽象类
 */
public abstract class SchoolReport {
    // 成绩单的主要展示的就是你的成绩情况
    public abstract void report();

    // 成绩单要家长签字,这个是最要命的
    public abstract void sign(String name);
}

然后看我们的实现类 FouthGradSchoolReport:

package com.alex.patterns.decorator;

/**
 * @author Alex
 *  四年级的成绩单,这个是我们学校第一次实施,以前没有干过 这种“缺德”事。
 */
public class FouthGradeSchoolReport extends SchoolReport {
    // 我的成绩单
    public void report() {
        // 成绩单的格式是这个样子的
        System.out.println("尊敬的XXX家长:");
        System.out.println(" ......");
        System.out.println(" 语文 62 数学65 体育 98 自然 63");
        System.out.println(" .......");
        System.out.println(" 家长签名: ");
    }

    // 家长签名
    public void sign(String name) {
        System.out.println("家长签名为:" + name);
    }

}

成绩单出来,你别看什么 62,65 之类的成绩,你要知道在小学低于 90 分基本上就是中下等了,唉,
爱学习的人太多了!怎么着,那我把这个成绩单给老爸看看?好,我们修改一下类图,成绩单给老爸看:

这里写图片描述

老爸开始看成绩单,这个成绩单可是最真实的,啥都没有动过,原装,看 Father 类:

package com.alex.patterns.decorator;

/**
 * @author Alex
 * 老爸看成绩单了
 */
public class Father {
    public static void main(String[] args) {
        // 成绩单拿过来
        SchoolReport sr = new FouthGradeSchoolReport();
        // 看成绩单
        sr.report();
        // 签名?休想!
    }
}

就这成绩还要我签字?!老爸就开始找笤帚,我的屁股已经做好了准备,肌肉要绷紧,要不那个太疼了!哈
哈,幸运的是,这个不是当时的真实情况,我没有直接把成绩单交给老爸,而是在交给他之前做了点技术工作,
我要把成绩单封装一下,封装分类两步走:
第一步:跟老爸说各个科目的最高分,语文最高是 75,数学是 78,自然是 80,然老爸觉的我成绩与最高分
数相差不多,这个是实情,但是不知道是什么原因,反正期末考试都考的不怎么样,但是基本上都集中在 70 分
以上,我这 60 多分基本上还是垫底的角色;
第二步:在老爸看成绩单后,告诉他我是排名第 38 名,全班,这个也是实情,为啥呢?有将近十个同学退
学了!这个情况我是不说的。不知道是不是当时第一次发成绩单,学校没有考虑清楚,没有写上总共有多少同学,
排名第几名等等,反正是被我钻了个空子。
那修饰是说完了,我们看看类图如何修改:

这里写图片描述

我想这是你最容易想到的类图,通过直接增加了一个子类,重写 report 方法,很容易的解决了这个问题,
是不是这样?是的,确实是,确实是一个很好的办法,我们来看具体的实现:

package com.alex.patterns.decorator;

/**
 * @author Alex
 * 对这个成绩单进行美化 Sugar这个词太好了,名词是糖的意思,动词就是美化 给你颗糖你还不美去
 */
public class SugarFouthGradeSchoolReport extends FouthGradeSchoolReport {
    // 首先要定义你要美化的方法,先给老爸说学校最高成绩
    private void reportHighScore() {
        System.out.println("这次考试语文最高是75,数学是78,自然是80");
    }

    // 在老爸看完毕成绩单后,我再汇报学校的排名情况
    private void reportSort() {
        System.out.println("我是排名第38名...");
    }

    // 由于汇报的内容已经发生变更,那所以要重写父类
    @Override
    public void report() {
        this.reportHighScore(); // 先说最高成绩
        super.report(); // 然后老爸看成绩单
        this.reportSort(); // 然后告诉老爸学习学校排名
    }
}

然后 Father1 类稍做修改就可以看到美化后的成绩单,看代码如下:

package com.alex.patterns.decorator;

/**
 * @author Alex
 * 老爸看成绩单了
 */
public class Father1 {
    public static void main(String[] args) {
        // 美化过的成绩单拿过来
        SchoolReport sr = new SugarFouthGradeSchoolReport();
        // 看成绩单
        sr.report();
        // 然后老爸,一看,很开心,就签名了
        sr.sign("老三"); // 我叫小三,老爸当然叫老三
    }
}

通过继承确实能够解决这个问题,老爸看成绩单很开心,然后就给签字了,但是现实的情况很复杂的,可能
老爸听我汇报最高成绩后,就直接乐开花了,直接签名了,后面的排名就没必要了,或者老爸要先听排名情况,
那怎么办?继续扩展类?你能扩展多少个类?这还是一个比较简单的场景,一旦需要装饰的条件非常的多,比如
20 个,你还通过继承来解决,你想想的子类有多少个?你是不是马上就要崩溃了!
好,你也看到通过继承情况确实出现了问题,类爆炸,类的数量激增,光写这些类不累死你才怪,而且还要
想想以后维护怎么办,谁愿意接收这么一大堆类的维护哪?并且在面向对象的设计中,如果超过 2 层继承,你就
应该想想是不是出设计问题了,是不是应该重新找一条道了,这是经验值,不是什么绝对的,继承层次越多你以
后的维护成本越多,问题这么多,那怎么办?好办,装饰模式出场来解决这些问题,我们先来看类图:

这里写图片描述

增加一个抽象类和两个实现类,其中 Decorator 的作用是封装 SchoolReport 类,看源代码:

package com.alex.patterns.decorator;

/**
 * @author Alex
 * 装饰类,我要把我的成绩单装饰一下
 */
public abstract class Decorator extends SchoolReport {
    // 首先我要知道是那个成绩单
    private SchoolReport sr;

    // 构造函数,传递成绩单过来
    public Decorator(SchoolReport sr) {
        this.sr = sr;
    }

    // 成绩单还是要被看到的
    public void report() {
        this.sr.report();
    }

    // 看完毕还是要签名的
    public void sign(String name) {
        this.sr.sign(name);
    }
}

Decorator 抽象类的目的很简单,就是要让子类来对封装 SchoolReport 的子类,怎么封装?重写
report 方法!先看 HighScoreDecorator 实现类:

package com.alex.patterns.decorator;

/**
 * @author Alex
 * 我要把我学校的最高成绩告诉老爸
 */
public class HighScoreDecorator extends Decorator {
    // 构造函数
    public HighScoreDecorator(SchoolReport sr) {
        super(sr);
    }

    // 我要汇报最高成绩
    private void reportHighScore() {
        System.out.println("这次考试语文最高是75,数学是78,自然是80");
    }

    // 最高成绩我要做老爸看成绩单前告诉他,否则等他一看,就抡起笤帚有揍我,我那还有机会说呀
    @Override
    public void report() {
        this.reportHighScore();
        super.report();
    }
}

重写了 report 方法,先调用具体装饰类的装饰方法 reportHighScore,然后再调用具体构件的方法,
我们再来看怎么回报学校排序情况 SortDecorator 代码:

package com.alex.patterns.decorator;

/**
 * @author Alex
 * 学校排名的情况汇报
 */
public class SortDecorator extends Decorator {
    // 构造函数
    public SortDecorator(SchoolReport sr) {
        super(sr);
    }

    // 告诉老爸学校的排名情况
    private void reportSort() {
        System.out.println("我是排名第38名...");
    }

    // 老爸看完成绩单后再告诉他,加强作用
    @Override
    public void report() {
        super.report();
        this.reportSort();
    }
}

然后看看我老爸怎么看成绩单的:

package com.alex.patterns.decorator;

/**
 * @author Alex
 * 老爸看成绩单了 第  134  页 您的设计模式
 */
public class Father2 {
    public static void main(String[] args) {
        // 成绩单拿过来
        SchoolReport sr;
        sr = new FouthGradeSchoolReport(); // 原装的成绩单
        // 加 了最高分说明的成绩单
        sr = new HighScoreDecorator(sr);
        // 又加了成绩排名的说明
        sr = new SortDecorator(sr);
        // 看成绩单
        sr.report();
        // 然后老爸,一看,很开心,就签名了
        sr.sign("老三"); // 我叫小三,老爸当然叫老三
    }
}

老爸一看成绩单,听我这么一说,非常开心,儿子有进步呀,从 40 多名进步到 30 多名,进步很大,
躲过了一顿海扁。
这就是装饰模式,装饰模式的通用类图如下:

这里写图片描述

看类图,Component 是一个接口或者是抽象类,就是定义我们最核心的对象,也就是最原始的对象,比
如上面的成绩单,记住在装饰模式中,必然有一个被提取出来最核心、最原始、最基本的接口或抽象类,
就是 Component。
ConcreteComponent 这个事最核心、 最原始、 最基本的接口或抽象类的实现, 你要装饰的就是这个东东。
Decorator 一般是一个抽象类, 做什么用呢?实现接口或者抽象方法, 它里面可不一定有抽象的方法呀,
在它的属性里必然有一个 private 变量指向 Component。
ConcreteDecoratorA 和 ConcreteDecoratorB 是两个具体的装饰类,你要把你最核心的、最原始的、最
基本的东西装饰城啥东西,上面的例子就是把一个比较平庸的成绩单装饰成家长认可的成绩单。
装饰模式是对继承的有力补充,你要知道继承可不是万能的,继承可以解决实际的问题,但是在项目
中你要考虑诸如易维护、易扩展、易复用等,而且在一些情况下(比如上面那个成绩单例子)你要是用继
承就会增加很多了类,而且灵活性非常的差,那当然维护也不容易了,也就是说装饰模式可以替代继承,
解决我们类膨胀的问题,你要知道继承是静态的给类增加功能,而装饰模式则是动态的给增加功能,你看
上面的那个例子,我不想要 SortDecorator 这层的封装也很简单呀,直接在 Father 中去掉就可以了,如果
你用继承就必须修改程序。
装饰模式还有一个非常好的优点,扩展性非常好,在一个项目中,你会有非常多因素考虑不到,特别
是业务的变更,时不时的冒出一个需求,特别是提出一个令项目大量延迟的需求时候,那种心情是…,真
想骂娘!装饰模式可以给我们很好的帮助,通过装饰模式重新封装一个类,而不是通过继承来完成,简单
点说,三个继承关系 Father,Son,GrandSon 三个类,我要再 Son 类上增强一些功能怎么办?我想你会坚决
的顶回去!不允许,对了,为什么呢?你增强的功能是修改 Son 类中的方法吗?增加方法吗 ?对 GrandSon
的影响哪?特别是 GrandSon 有多个的情况,你怎么办?这个评估的工作量就是够你受的,所以这个是不允
许的,那还是要解决问题的呀,怎么办?通过建立 SonDecorator 类来修饰 Son,等于说是创建了一个新的
类,这个对原有程序没有变更,通过扩充很好的完成了这次变更。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值