前言:在学习IO的时候发现,InputStreamReader这个转换字节流和字符流的桥梁。
这正是IO设计的核心思想:装饰模式。
主要参考:《设计模式之禅》

定义

装饰模式:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更为灵活。
也就是说,在不影响原有对象的前提下,无侵入的给一个对象增一些额外的功能。而比生成子类更为灵活则是体现在组合职责的情况(下文会分析)。

表述 & 代码

学校有成绩单,这次小明需要拿着期末成绩单去找他爸签名。但是小明爸如果直接看到期末成绩的分数太少,不仅不会签名,还会揍他一顿。
Report

/**
 * 成绩单,给家长看然后要签名
 *
 * @author CaiTianXin
 * @date 2021/3/15 15:59
 */
public abstract class Report {
    /**
     * 展示成绩
     */
    public abstract void report();

    /**
     * 家长签名
     *
     * @param parent 家长名字
     */
    public abstract void sign(String parent);
}

FinalReport

/**
 * 期末成绩单
 *
 * @author CaiTianXin
 * @date 2021/3/15 16:12
 */
public class FinalReport extends Report{
    @Override
    public void report() {
        System.out.println("尊敬的家长");
        System.out.println("  ...");
        System.out.println("  小明:语文16,数学23");
        System.out.println("  ...");
        System.out.println("家长签名:");
    }

    @Override
    public void sign(String parent) {
        System.out.println(parent);
    }
}

Father

/**
 * 你爹拿到成绩单,考得好签,考不好不签且揍你
 *
 * @author CaiTianXin
 * @date 2021/3/15 16:31
 */
public class Father {
    public static void main(String[] args) {
        scenes1();
    }

    public static void scenes1() {
        // 拿过来成绩单
        Report report = new FinalReport();
        // 一看成绩
        report.report();
        // 成绩太烂不给签,且要打你
//        report.sign("小明爸");
    }
}


所以需要在他看到成绩前先告知班级最高分来说明一下这次很难,大家都考不好;其次还要再看完后补充一下这次自己的排名(进步了)。这样小明爸也就肯签名了。

普通代码

没有接触装饰模式前,大概都会觉得:
直接写一个美化期末成绩的子类继承期末成绩单,然后拿给父亲看就行了。

咋一看没什么问题,代码顺便也写了出来:

SugarFinalReport

/**
 * 不建议的写法
 *
 * @author CaiTianXin
 * @date 2021/3/16 11:35
 */
public class SugarFinalReport extends FinalReport{

    private void reportHighScore(){
        System.out.println("(明:这次考试特别难!语文最高才61,数学才63)");
    }

    private void reportSort(){
        System.out.println("(明:我这次是倒二,不是倒一了!)");
    }

    @Override
    public void report() {
        this.reportHighScore();
        super.report();
        this.reportSort();
    }
}

Father

/**
 * 你爹拿到成绩单,考得好签,考不好不签且揍你
 *
 * @author CaiTianXin
 * @date 2021/3/15 16:31
 */
public class Father {
    public static void main(String[] args) {
        scenes3();
    }

    public static void scenes3(){
        Report report = new SugarFinalReport();
        report.report();
        report.sign("小明爸");
    }
}


那不妨思考一下:
如果这种情况下,需求有变需要怎么改动呢?
拿最高分和排名来举例的话。其实需求可能会变成:只看高分、只看排名、先高分再排名、先排名再高分。
对于本类的继承方案来说,最坏情况下需要写出4种Sugar类去彼此嵌套修改或继承。
而对于装饰类来说,这些组合操作通通丢给了被装饰者的调用方。

另一个思考:既然继承实现那么复杂且难以维护,为什么不统统丢在一个美化类中呢?
哲是说这样子违背了单一职责的思维。我还在思考。

装饰模式代码

对于装饰类来说,它减少的代码量或者说维护成本在于 组合成本 的开销。

说明:通过一个抽象类Decorator去继承Report,并且两个实现类去继承Decorator,特殊代理了FinalReport类(Father类中new的是FinalReport)
Decorator

/**
 * 装饰类
 *
 * @author CaiTianXin
 * @date 2021/3/15 16:49
 */
public abstract class Decorator extends Report{
    /**
     * 要装饰的对象
     */
    private Report report;

    public Decorator(Report report){
        this.report = report;
    }

    /**
     * 成绩该看还是要被看的
     * 动作依旧是委托给被装饰对象
     */
    @Override
    public void report() {
        this.report.report();
    }

    /**
     * 签还是得签的
     * 动作依旧是委托给被装饰对象
     * @param parent 家长名字
     */
    @Override
    public void sign(String parent) {
        this.report.sign(parent);
    }
}

HighScoreDecorator

/**
 * 最高成绩装饰
 *
 * @author CaiTianXin
 * @date 2021/3/16 11:21
 */
public class HighScoreDecorator extends Decorator{

    public HighScoreDecorator(Report report) {
        super(report);
    }

    /**
     * 打预防针说下这次很难,大家都考不好
     */
    private void reportHighScore(){
        System.out.println("(明:这次考试特别难!语文最高才61,数学才63)");
    }

    @Override
    public void report() {
        // 看前先说下其他人也很拉跨
        this.reportHighScore();
        super.report();
    }
}

SortDecorator

/**
 * 排名情况装饰
 *
 * @author CaiTianXin
 * @date 2021/3/15 17:31
 */
public class SortDecorator extends Decorator{

    public SortDecorator(Report report) {
        super(report);
    }

    /**
     * 汇报排名情况
     */
    private void reportSort(){
        // 原倒二生病请假了
        System.out.println("(明:我这次是倒二,不是倒一了!)");
    }

    @Override
    public void report() {
        super.report();
        // 后置增强
        this.reportSort();
    }
}

Father三种差异对比

/**
 * 你爹拿到成绩单,考得好签,考不好不签且揍你
 *
 * @author CaiTianXin
 * @date 2021/3/15 16:31
 */
public class Father {
    public static void main(String[] args) {
//        scenes1();
        scenes2();
//        scenes3();
    }

    public static void scenes1() {
        // 拿过来成绩单
        Report report = new FinalReport();
        // 一看成绩
        report.report();
        // 成绩太烂不给签,且要打你
//        report.sign("小明爸");
    }

    /**
     * 差异:各种增强装饰的组合是在这里执行的,保证了装饰子类的原子性
     */
    public static void scenes2(){
        // 拿过来成绩单(先加了最高分说明再加了排名)
        Report report = new SortDecorator(new HighScoreDecorator(new FinalReport()));
        // 一看成绩
        report.report();
        // 大家都烂、而且你排名还进步了,高兴,签了
        report.sign("小明爸");
    }

    /**
     * 差异:而这种写法则是在内部排序
     */
    public static void scenes3(){
        Report report = new SugarFinalReport();
        report.report();
        report.sign("小明爸");
    }
}


装饰模式的通用模板就是,抽象的装饰类去继承 被装饰者的父类,且new的为被装饰者。从而达到代理代理被装饰者的目的。而其它装饰实现类则继承装饰类,分别做各自的职责。

优缺点

优点

正如我们所看到的,它不像继承关系那样纵向套娃,而是动态的横向扩展某一实现类的功能,它更像是继承关系的一种替代方案。无论装饰多少层,实现还是is-a的关系。

继承是静态地给类增加功能,而装饰模式则是动态地增加功能。比如装饰类中去掉SortDecorator这层的封装也很简单,于是直接在Father中去掉就可以了,如果你用继承就必须修改程序。

缺点

它的缺点也很明显,它只是把继承关系那种纵向套娃移到了上游而已:Report report = new SortDecorator(new HighScoreDecorator(new FinalReport()));。对于出错的排查来说也是很麻烦,所以建议还是减少装饰类的数量,尽可能的把相同类型的功能写在一块。

适用场景

  1. 要扩展一个类的功能或者增加附加功能。
  2. 动态给对象增加功能,且可以动态撤销。(其实跟上一点相差无几)
  3. 为一批兄弟类改装或加装功能。
Last modification:March 18th, 2021 at 10:17 am
喵ฅฅ