Loading... > 前言:在学习IO的时候发现,InputStreamReader这个转换字节流和字符流的桥梁。 > 这正是IO设计的核心思想:装饰模式。 > 主要参考:《设计模式之禅》 <!--more--> ## 定义 **装饰模式:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更为灵活。** 也就是说,在不影响原有对象的前提下,无侵入的给一个对象增一些额外的功能。而比生成子类更为灵活则是体现在组合职责的情况(下文会分析)。 ## 表述 & 代码  学校有成绩单,这次小明需要拿着期末成绩单去找他爸签名。但是小明爸如果直接看到期末成绩的分数太少,不仅不会签名,还会揍他一顿。 **`Report`** ```java /** * 成绩单,给家长看然后要签名 * * @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`** ```java /** * 期末成绩单 * * @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`** ```java /** * 你爹拿到成绩单,考得好签,考不好不签且揍你 * * @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`** ```java /** * 不建议的写法 * * @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`** ```java /** * 你爹拿到成绩单,考得好签,考不好不签且揍你 * * @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`** ```java /** * 装饰类 * * @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`** ```java /** * 最高成绩装饰 * * @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`** ```java /** * 排名情况装饰 * * @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`**三种差异对比 ```java /** * 你爹拿到成绩单,考得好签,考不好不签且揍你 * * @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:August 22, 2022 © Allow specification reprint Like 0 喵ฅฅ