设计模式之装饰模式

67 阅读3分钟

案例:学生成绩单,4年级结束后,学校要求,家长在期末考试成绩单上签字,学生才能升入5年级

类图:

代码:

/**
 * @Description: 成绩单抽象类
 */
public abstract class SchoolReport {

    /**
     * 报告成绩
     */
    public abstract void report();

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

}

/**
 * @Description: 四年级学生成绩单
 */
public class FouthGradeSchoolReport extends SchoolReport {

    @Override
    public void report() {
        System.out.println("四年级学生成绩单");
        System.out.println("语文:62");
        System.out.println("数学:65");
        System.out.println("体育:98");
        System.out.println("...");
    }

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

public class Father {

    public static void main(String[] args) {

        SchoolReport report = new FouthGradeSchoolReport();
        report.report();

        // 由于语文数据成绩太差,不能签字
        // report.sign("张三");

    }

}

由于语文数学成绩差,所以家长不能签字,所以需要把成绩单封装一下。

  1. 说明语文成绩全班最高分为75,数学成绩全班最高分为78,这样就感觉62和65分也能接受
  2. 说明全班排名是38名,但是全班有50人,这个成绩还不算垫底

修改类图:

修改代码:

/**
 * @Description: 装饰后的成绩单
 */
public class SugarFouthGradeSchoolReport extends FouthGradeSchoolReport {

    private void reportHighScore() {
        System.out.println("语文成绩最高为75,数学成绩最高为78");
    }

    private void reportSort() {
        System.out.println("班级排名38");
    }

    /**
     * 重写 report() 方法,装饰汇报内容
     */
    @Override
    public void report() {
        // 先说明最高成绩
        reportHighScore();
        // 再汇报自己的成绩
        super.report();
        // 再汇报班级排名
        reportSort();
    }
}

public class Father {

    public static void main(String[] args) {

        SchoolReport report = new SugarFouthGradeSchoolReport();
        report.report();

        // 经过装饰的成绩单被家长接受了,可以签字
        report.sign("张三");

    }

}

通过继承的方式对父类进行装饰,存在以下问题:

  • 装饰方法的顺序问题,目前是硬编码
  • 如果继续对SugarFouthGradeSchoolReport类进行装饰,则需要开发其子类,导致类的继承层数过多

装饰模式正是为了解决这些问题,修改类图:

代码:

/**
 * @Description: 装饰抽象类,封装被装饰类 SchoolReport
 */
public abstract class Decorator extends SchoolReport {

    private SchoolReport report;

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

    @Override
    public void report() {
        report.report();
    }

    @Override
    public void sign(String name) {
        report.sign(name);
    }
}

/**
 * @Description: 装饰类具体实现 - 汇报最高成绩
 */
public class HighScoreDecorator extends Decorator {

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

    private void reportHighScore() {
        System.out.println("语文成绩最高为75,数学成绩最高为78");
    }

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

}

/**
 * @Description:装饰类具体实现 - 汇报班级排名
 */
public class SortDecorator extends Decorator {

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

    private void reportSort() {
        System.out.println("班级排名38");
    }

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

}

public class Father {

    public static void main(String[] args) {

        // 原始成绩单
        SchoolReport report = new FouthGradeSchoolReport();

        // 增加装饰
        report = new HighScoreDecorator(report);
        report = new SortDecorator(report);

        // 汇报
        report.report();

        // 签字
        report.sign("张三");

    }

}

装饰模式的通用类图如下:

  • Component是一个接口或者是抽象类,用于定义需要被装饰的核心类
  • ConcreteComponent:需要被装饰的类的具体实现类,实现装饰的是这个类
  • Decorator:一般是一个抽象类,其中封装Component属性
  • ConcreteDecoratorA、ConcreteDecoratorB:具体的装饰类,实现装饰功能

装饰模式是对继承的有力补充,可以替代继承解决类膨胀的问题,继承是静态的给类增加功能,而装饰模式是动态地给类增加功能,而且装饰模式的扩展性好。比如上面的例子中,如果要取消班级排名这个装饰,只需要修改客户端类Father即可,减少一行代码而已,而要增加一个装饰功能,只要增加一个装饰的具体实现类以及在客户端类中增加一行代码即可。

本文原书:《您的设计模式》作者:CBF4LIFE