12.1 Decorator 模式

  假如有一块蛋糕,如果只涂上奶油,其他什么也不加,就是奶油蛋糕。如果加上草莓,就是草莓奶油蛋糕。如果再加上一块黑色巧克力板,上面用白色巧克力写上姓名,然后插上蜡烛,就变成了一块生日蛋糕。
  不论是蛋糕、奶油蛋糕、草莓奶油蛋糕,他们的核心都是蛋糕。不过,经过一系列装饰后,蛋糕的味道变得更加甜美了,目的也变得更加明确了。
  程序中的对象与蛋糕十分相似。首先有一个相当于蛋糕的对象,然后不断地装饰蛋糕一样地不断地对齐增加功能,它就编程了使用目的更加明确的对象。
  像这样不断地为对象添加装饰的设计模式被称为 Decorator 模式。

12.2 示例程序

  示例程序的功能是给文字添加装饰边框。指用 “-”、“\”、“+”、“|” 组成的边框。

示例程序类图

|| Display 类

  Display 类是可以显示多行字符串的抽象类。
  getColumns 方法和 getRows 方法分别用于获取横向字符数和纵向行数。都是抽象方法需要子类实现。getRowText 方法用户获取指定的某一行的字符串。
  show 是显示所有行的字符串方法。在show 内部,程序会调用 getRows 方法获取行数,调用 getRowText 方法获取改行需要显示的字符串,然后循环显示字符串。属于 Template Method 模式。

public abstract class Display {public abstract int getColumns(); // 获取横向字符数public abstract int getRows(); // 获取行数public abstract String getRowText(int row); // 获取 row 行的字符串public void show() { // 打印所有for (int i = 0; i < getRows(); i++) {System.out.println(getRowText(i));}}
}
|| StringDisplay 类

  StringDisplay 类是用于显示单行字符串的类。是 Display 的子类,因此肩负着实现 Display 类中声明的抽象方法的重任。
  string 字段中保存的是要显示的字符串。由于 StringDisplay 类只显示一行字符串,因此 getColumns 返回 string.getBytes().length 的值,getRows 方法返回固定值1。
  仅当要获取第 0 行时 getRowText 方法才会返回 string 字段。以本章开头的蛋糕的比喻来说, StringDisplay 类就相当于生日蛋糕中的核心蛋糕。

/**
* 显示核心类.
* 只显示一行字符串。
*/
public class StringDisplay extends Display{private String string;public StringDisplay(String string) {this.string = string;}@Overridepublic int getColumns() {return string.length();}@Overridepublic int getRows() {return 1;}@Overridepublic String getRowText(int row) {if (row == 0) {return string;}return null;}
}
|| Border 类

  Border 类是装饰边框的抽象类。虽然它所表示的是装饰边框,但它也是 Display 的子类。
  也就是说,通过继承,装饰边框与被装饰物具有了相同的方法。具体而言,Border 类继承了父类的各方法。从接口角度而言,装饰边框与被装饰物具有相同的方法也就意味着它们具有一致性。
  在装饰边框 Border 类中有一个 Display 类型的 display 字段,它表示装饰物。不过,其所表示的装饰物并不仅限于 StringDisplay 的实例。因为 Border 也是 Display 的子类,display 字段也可以表示其他的装饰边框,而且那个边框中也有一个 display 字段。

/**
* 装饰边框的抽象类.
*/
public abstract class Border extends Display {protected Display display;protected Border(Display display) {this.display = display;}}
|| SideBorder 类

  SideBorder 类是一种具体的装饰边框,是 Border 类的子类。SideBorder 类是指定的字符装饰字符串的左右两侧。例如,字符为 ‘|’
|被装饰物|
  getColumns 方法是用于获取横向字符数的方法。只需要在被装饰物的字符数的基础上,再加上两侧边框的字符数即可。

1 + display.getColumns() + 1

  因为 SideBorder 类并不会在字符串的上下两侧添加字符,因此 getRows 方法直接返回 display.getRows() 即可。
  getRowText 方法可以获取参数指定的那一行的字符数,因此,像下面这样,在 display.getRowText(row) 的字符串两侧,加上 borderchar 这个装饰边框即可。

borderchar + display.getRowText(row) + borderchar

/**
* 具体的装饰器-在两端加上字符
*/
public class SideBorder extends Border {private char borderChar;protected SideBorder(Display display, char ch) {super(display);this.borderChar = ch;}@Overridepublic int getColumns() {return 1 + display.getColumns() + 1; // 因为在两端加上字符,可以表示为 1 + 字符数 + 1}@Overridepublic int getRows() {return display.getRows(); // 未改变 Row}@Overridepublic String getRowText(int row) {return borderChar + display.getRowText(row) + borderChar;}
}
|| FullBorder 类

  FullBorder 类与 sideBorder 类相似。其会在字符串的上下左右都加上装饰边框。

/**
* 在字符串的上下左右都加上装饰边框.
* +-----+
* |Hello|
* +-----+
*/
public class FullBorder extends Border {public FullBorder(Display display) {super(display);}@Overridepublic int getColumns() {return 1 + display.getColumns() + 1;}@Overridepublic int getRows() {return 1 + display.getRows() + 1;}@Overridepublic String getRowText(int row) {if (row == 0) {return '+' + makeLine('-', display.getColumns()) + '+'; // 上边框} else if (row == display.getRows() + 1) {return '+' + makeLine('-', display.getColumns()) + '+'; // 下边框} else {return '|' + display.getRowText(row - 1) + '|'; // 其他边框}}// 用于连续的显示某个字符private String makeLine(char ch, int count) {StringBuilder sb = new StringBuilder();for (int i = 0; i < count; i++) {sb.append(ch);}return sb.toString();}
}
|| Main 类

  用于测试程序行为的类。b4 用于加多重边框。

public class Main {public static void main(String[] args) {Display b1 = new StringDisplay("Hello, world.");Display b2 = new SideBorder(b1, '#');Display b3 = new FullBorder(b2);b1.show();b2.show();b3.show();Display b4 = new SideBorder(new FullBorder(new FullBorder(new SideBorder(new FullBorder(new StringDisplay("Hello, World.")), '*'))), '/');b4.show();}
}

运行结果:

Hello, world.
#Hello, world.#
+---------------+
|#Hello, world.#|
+---------------+
/+-------------------+/
/|+-----------------+|/
/||*+-------------+*||/
/||*|Hello, World.|*||/
/||*+-------------+*||/
/|+-----------------+|/
/+-------------------+/

12.3 Decorator 模式中登场角色

  在 Decorator 模式中有以下登场角色。
  ◆ Component
  增加功能时的核心角色。以开头场景为例,装饰前的蛋糕就是 Component 角色。Component 角色只是定义了蛋糕的接口(API)。在示例程序中,由 Display 类扮演此角色。
  ◆ ConcreteComponent
  该角色是实现了 Component 角色所定义的接口的具体蛋糕。在示例程序中,由 StringDisplay 类扮演此角色。
  ◆ Decorator(装饰物)
  该角色具有与 Component 角色相同的接口。在它内部保存了被装饰对象 - Component 角色。Decorator 角色知道自己要装饰的对象。在示例程序中,由 Border 类扮演此角色。
  ◆ ConcreteDecorator (具体的装饰物)
  该角色是具体的 Decorator 角色。在示例程序中,由 SideBorder 类和 FullBorder 类扮演此角色。

Decorator 模式的类图

12.4 拓展思路的要点

|| 接口(API)的透明性

  在 Decorator 模式中,装饰边框与被装饰物具有一致性。具体而言,在示例程序中,表示装饰边框的 Border 类是表示被装饰物的 Display 类的子类,这就体现了它们之间的一致性。也就是说 Border 类(及其子类)与表示被装饰物的 Display 类具有相同的接口。
  这样,即使被装饰物被边框装饰起来了,接口(API)也不会被隐藏起来。其他类依然可以调用 getColumns、getRows、getRowText 以及 show 方法。这就是接口(API)的透明性。
  得益于接口的透明性,Decorator 模式中也形成了类似于 Composite 模式中的递归结构。也就是说,装饰边框里面的 “被装饰物” 实际上又是别的物体的 “装饰边框” 。不过,Decorator 模式虽然与 Composite 模式一样,都具有递归结构,但是它们的使用目的不同。Decorator 模式主要的是通过添加装饰物来增加对象的功能。

|| 在不改变被装饰物的前提下增加功能

  在 Decorator 模式中,装饰边框与被装饰物具有相同的接口(API)。虽然接口是相同的,但装饰越多,功能则越多。例如,用 SideBorder 装饰 Display 后,就可以在字符串的左右两侧加上装饰字符。如果再用 FullBorder 装饰,那么就可以在字符串的四周加上边框。此时,我们完全不需要对被装饰的类做任何修改。这样,我们就实现了不修改被装饰的类即可增加功能

|| 可以动态的增加功能

  Decorator 模式中用到了委托,它使类之间形成了弱关联关系。因此,不用改变框架代码,就可以生成一个与其他对象具有不同关系的新对象。

|| 只需要一些装饰物即可添加许多功能

  使用 Decorator 模式可以为程序添加许多功能。只要准备一些装饰边框,即使这些装饰边框都只具有非常简单的功能,也可以将它们自由组合成为新的对象。
  就像我们可以自由选择香草味冰激凌、巧克力冰激凌、草莓味冰激凌一样。如果冰激凌店要为顾客准备冰激凌成品那就太麻烦了。因此,冰激凌店只会准备各种香料,当顾客下单后只需要在冰激凌上加上各种香料就可以了。Decorator 模式就是可以应对这种多功能对象的需求的一种模式。

|| java.io 包与 Decorator 模式

  java.io 包是用于输入输出的包。在其中,使用了 Decorator 模式。
  首先,可以像下面这样生成一个读取文件的实例。

Render render = new FileReader("data.txt");

  然后,像下面这样在读取文件时将文件内容放入缓冲区。

Reader reader = new BufferedReader(new FileReader("data.txt"));

  这样,在生成 BufferedReader 类的实例时,会指定将文件读取到 FileReader 类的实例中。
  在然后,我们可以想下面这样管理行号。

Reader reader = new LineNumberReader(new BufferedReader(new FileReader("data.txt")));

  可以看出,无论是 LineNumberReader 还是 BufferedReader 类的构造器,都可以接收 Reader 类的实例作为参数。
  除了 java.io 包以外,还在 javax.swing.border 包中使用了 Decorator 模式。其为我们提供了可为界面中的控件添加装饰边框的类。

|| 导致增加许多很小的类

  Decorator 模式的一个缺点就是会导致程序增加许多功能类似的很小的类。

12.6 延伸阅读:继承和委托中的一致性

  我们再稍微了解一下 “一致性”,即 “可以将不同的东西当作同一种东西看待” 的知识。

|| 继承-父类与子类的一致性

  子类和父类具有一致性。如:

class Parent {...void parentMethod() {...}
}class Child extends Parent {...void childMethod() {...}
}

  此时,Child 类的实例可以保存在 Parent 类型的变量中,也可以调用从 Parent 类中继承的方法。也就是说,可以像操作 Parent 类的实例一样操作 Child 类的实例。这是将子类当作父类看待。
  但是,反过来,将父类当作子类一样操作,则需要先进行类型转换(必须是子类的对象)。

Parent obj = new Child();
((Child)obj).childMethod();
|| 委托-自己和被委托对象的一致性

  使用委托让接口具有透明性,自己和被委托对象具有一致性。
  下面我们来看一个稍微有点生硬的例子。

class Rose {Violet obj = ...void method() {obj.method();}
}class Violet {void method() {...}
}

  Rose 和 Violet 都有相同的 method 方法。Rose 将 method 方法的处理委托给了 Violet。这样会让人有一种好像这两个类有所关联,又好像没有关联的感觉。
  因为这两个类虽然都有 method 方法,但是却没有明确地在代码中提现出这个 “共通性”。如果要明确地表示 method 方法是共通的,只需要像下面这样编写一个共通的抽象类 Flower 就可以了。

abstract class Flower {abstract void method();
}class Rose extends Flower {Violet obj = ...void method() {obj.method();}
}class Violet extends Flower {void method() {...}
}

  或是将 Flower 作为接口也行。

interface Flower {void method();
}

  至此,可能会产生这样的疑问,即 Rose 类中的 Obj 字段被指定为具体的 Violet 真的好吗?如果指定为抽象类型 Flower 会不会更好呢?究竟应该怎么做才好,其实没有固定的答案,需求不同,做法也会不同。

Decorator(装饰器) 模式相关推荐

  1. Java —— Decorator 装饰器模式

    文章目录 Java -- Decorator 装饰器模式 简介 用处 简单例子 结构 代码 涉及角色 相关的设计模式 应用实例 优点 缺点 使用场景 注意事项 代码 Java -- Decorator ...

  2. Decorator 装饰器模式 -动态组合

    为什么80%的码农都做不了架构师?>>>    一:业务场景 奖金计算,每个部门,有不同的计算方法,且每个部门有不同类型的奖金项:而且每年或每隔几个季度奖金算法都要重新实现下. 这个 ...

  3. 6中结构型设计模式的对比理解(Composite组合模式,Proxy代理模式,Flyweight享元模式,Facade门面模式,Bridge桥接模式,Decorator装饰器模式)

    结构型模式 结构型模式用来组装 类和对象,以获得更大的结构. 结构型类模式,通过继承机制来组合接口或类.简单的例子就是多重继承,最后一个类拥有所有父类的性质.这个模式有助于独立开发一个协同类.另一个例 ...

  4. 装饰器模式与java.io包

    为什么80%的码农都做不了架构师?>>>    Decorator设计模式是典型的结构型模式(在GOF的那本模式的Bible中将模式分为:1.创建型模式:2.结构型模式:3.行为模式 ...

  5. 聊聊在Vue项目中使用Decorator装饰器

    戳蓝字" Web前端严选 " 关注我们哦 ! 前言 初衷: 前几天我在公司其它Vue项目中,发现了是用Decorator装饰器模式开发的,看起来整体代码还不错,于是就做了一下笔记分 ...

  6. decorator java_装饰器模式-Decorator(Java实现)

    装饰器模式-Decorator(Java实现) 装饰器模式允许向一个现有的对象添加新的功能, 同时又不改变其结构. 其中 "现有对象"在本文中是StringDisplay类. 添加 ...

  7. python中的装饰器、装饰器模式_python 设计模式之装饰器模式 Decorator Pattern

    #写在前面 已经有一个礼拜多没写博客了,因为沉醉在了<妙味>这部小说里,里面讲的是一个厨师苏秒的故事.现实中大部分人不会有她的天分.我喜欢她的性格:总是想着去解决问题,好像从来没有怨天尤人 ...

  8. JAVA设计模式-装饰器模式(Decorator)

    装饰器模式(Decorator) 为了某个实现类在不修改原始类的基础上进行动态地覆盖或者增加方法 采用--------装饰器模式 实现类要保持与原有类的层级关系 装饰器模式是一种特殊的适配器模式 拿适 ...

  9. 装饰器模式(Decorator Pattern)

    23种设计模式完整教程 介绍 装饰者模式(Decorator Pattern)是指在不改变原有对象的基础之上,将功能附加到对 象上,提供了比继承更有弹性的替代方案(扩展原有对象的功能),属于结构型模式 ...

  10. python 设计模式之装饰器模式 Decorator Pattern

    #写在前面 已经有一个礼拜多没写博客了,因为沉醉在了<妙味>这部小说里,里面讲的是一个厨师苏秒的故事.现实中大部分人不会有她的天分.我喜欢她的性格:总是想着去解决问题,好像从来没有怨天尤人 ...

最新文章

  1. OS_CORE.C(总结)
  2. NAACL 2021 | AWS AI 提出基于对比学习的端到端无监督聚类方法
  3. Apache-Guacamole windows11 远程控制
  4. php 字符串分割成两段,php 两个字符串分割合并的简单示例
  5. 3D版的TagView,效果很赞
  6. 华为注册鸿蒙商标与三海经,华为注册了一本《山海经》?除了鸿蒙商标,还有很多...
  7. Qt界面UI之QML初见(学习笔记四)
  8. python 报价_python基础教程_查询价格
  9. TCP/IP review之 静态路由
  10. android fota解决方案,Android智能终端FOTA方案的制作方法
  11. Java中IO流详细整合(含案例)
  12. jquery点击事件写法
  13. lua生成随机数,设置随机数种子
  14. Python数据分析 第一章 数据分析的概述
  15. mac m1无法读取移动硬盘
  16. windows8.1 KB2919355更新步骤
  17. Python读写Excel数据(指定某行某列)
  18. Winform BLE 蓝牙通信
  19. Java IO源码分析(四)——PrintStream
  20. 电子狗服务器登记到本机信息,电子狗一键升级 车友在线傻瓜化操作

热门文章

  1. 用QEMU搭建arm开发环境之三:编译BusyBox建立最简单的文件系统
  2. 学计算机比较出名的技校,中国最出名的8大技校 蓝翔技校仅排第三
  3. Nokia N800开发经验
  4. python小白练手集合
  5. 视频教程-C#软件安全控制实例视频教程-C#
  6. java 简易版本数据库连接池
  7. 微软中国为员工推出全新的 10 天 Holistic Health Time Off 假期!
  8. winxp计算机管理中服务详解-12
  9. Http登陆qq空间
  10. accordion(折叠面板)的使用