【设计模式】装饰者模式
一、简介
当我们需要对一个类(A)的功能进行扩展的时候,可以选择使用继承通过子类(B)来实现,但是如果后来又要对A类增加一些功能且其中一些功能原来我们在子类B中已经实现过了,这时候怎么办呢?
这时候只能再用子类C继承A类并把代码重写一遍,然后子类就越来越多难以维护且有很多重复代码不够灵活。
我们知道通过利用组合和委托可以在运行时具有继承行为的功能,且更加灵活,通过装饰者模式就可以写新代码添加新的功能而且无需修改现有的代码(A类中的代码)。
再用一个例子解释装饰者模式,假如现在有一块蛋糕,如果只涂上奶油就是奶油蛋糕,如果再加上草莓就是草莓奶油蛋糕,无论是蛋糕、奶油蛋糕还是草莓蛋糕,它们的核心都是蛋糕。
程序中的对象与蛋糕十分相似,首先有一个相当于蛋糕的对象,然后不断地装饰蛋糕一样地不断地对其增加功能,它就变成了使用目的更加明确的对象,这样不断地为对象添加装饰的设计模式就是装饰者模式。
很多项目都用到了装饰者模式,比如Java的I/O流FileInputStream,BufferedInputStream等、Mybatis框架中的CachingExecutor(有兴趣的可以去看看其源码)。
二、示例
示例程序是写一个给文字添加装饰边框的功能,比如下面这样。
+--------------+
|#Hello World.#|
+--------------+
类的功能表格
名字 | 说明 |
---|---|
Display | 用于显示字符串的抽象类 |
StringDisplay | 显示单行字符串的类 |
Border | 显示边框的抽象类 |
SideBorder | 用于显示左右边框的类 |
FullBorder | 用于显示左右边框的类 |
Main | 测试程序的类 |
类图如下:
Display
- /**
- * @author 2YSP
- * @date 2020/6/16 21:07
- */
- public abstract class Display {
- /**
- * 获取横向字符数
- *
- * @return
- */
- protected abstract int getColumns();
- /**
- * 获取纵向行数
- *
- * @return
- */
- protected abstract int getRows();
- /**
- * 获取第row行的字符串
- *
- * @param row
- * @return
- */
- protected abstract String getRowText(int row);
- /**
- * 显示全部(用到了模板方法)
- */
- public final void show() {
- for (int i = 0; i < getRows(); i++) {
- System.out.println(getRowText(i));
- }
- }
- }
StringDisplay
- /**
- * @author 2YSP
- * @date 2020/6/16 21:16
- */
- public class StringDisplay extends Display {
- private String string;
- public StringDisplay(String string){
- this.string = string;
- }
- protected int getColumns() {
- return string.getBytes().length;
- }
- protected int getRows() {
- return 1;
- }
- protected String getRowText(int row) {
- if (row == 0){
- return string;
- }else {
- return null;
- }
- }
- }
Border
- public abstract class Border extends Display{
- /**
- * 被装饰物
- */
- protected Display display;
- // 在生成实例时通过参数指定被装饰物
- protected Border(Display display){
- this.display = display;
- }
- }
SideBorder
- /**
- * 装饰字符串的两侧,如|被装饰物|
- * @author 2YSP
- * @date 2020/6/16 21:21
- */
- public class SideBorder extends Border {
- private char borderChar;// 修饰边框的字符
- protected SideBorder(Display display, char ch) {
- super(display);
- this.borderChar = ch;
- }
- protected int getColumns() {
- // 字符数=字符串字符数加两侧边框字符数
- return 1 + display.getColumns() + 1;
- }
- protected int getRows() {
- return display.getRows();
- }
- protected String getRowText(int row) {
- return borderChar + display.getRowText(row) + borderChar;
- }
- }
FullBorder
- /**
- * @author 2YSP
- * @date 2020/6/16 21:26
- */
- public class FullBorder extends Border {
- protected FullBorder(Display display) {
- super(display);
- }
- protected int getColumns() {
- return 1 + display.getColumns() + 1;
- }
- protected int getRows() {
- // 行数为被装饰物的行数加上上下边框的行数
- return 1 + display.getRows() + 1;
- }
- protected 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 builder = new StringBuilder();
- for (int i = 0; i < count; i++) {
- builder.append(ch);
- }
- return builder.toString();
- }
- }
Main类
- 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(" 你好,世界。")), '*')
- )), '/');
- b4.show();
- }
- }
运行main方法控制打印结果如下:
Hello World.
#Hello World.#
+--------------+
|#Hello World.#|
+--------------+
/+-------------------------+/
/|+-----------------------+|/
/||*+-------------------+*||/
/||*| 你好,世界。|*||/
/||*+-------------------+*||/
/|+-----------------------+|/
/+-------------------------+/
Decorator模式中的角色:
- Component
增加功能时的核心角色,装饰前的蛋糕就是Component角色,示例中的Display也是。
- ConcreteComponent
该角色是实现了Component角色所定义接口的具体蛋糕,示例中就是StringDisplay。
- Decorator(装饰物)
该角色与Component角色具有相同的API,在它内部保存了被装饰对象(Component角色),示例中就是Border。
- ConcreteDecorator
具体装饰物,示例中的FullBorder和SideBorder
三、总结
目前还没在实际项目中用到过这种设计模式,但是存在即合理肯定是有用的,代码已上传Github点击这里查看。