设计模式学习(十二):Decorator装饰器模式

news2024/9/21 12:21:54

一、什么是Decorator模式

假如现在有一块蛋糕,如果只涂上奶油,其他什么都不加,就是奶油蛋糕。如果加上草莓,就是草莓奶油蛋糕。如果再加上一块黑色巧克力板,上面用白色巧克力写上姓名,然后插上代表年龄的蜡烛,就变成了一块生日蛋糕。

不论是蛋糕、奶油蛋糕、草莓蛋糕还是生日蛋糕,它们的核心都是蛋糕。不过,经过涂上奶油,加上草莓等装饰后,蛋糕的味道变得更加甜美了,目的也变得更加明确了。

程序中的对象与蛋糕十分相似。首先有一个相当于蛋糕的对象,然后像不断地装饰蛋糕一样地不断地对其增加功能,它就变成了使用目的更加明确的对象。

像这样不断地为对象添加装饰的设计模式被称为Decorator模式。Decorator指的是“装饰物”。

Decorator模式保证装饰边框与被装饰物的一致性。

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

二、Decorator模式示例代码

本章中的示例程序的功能是给文字添加装饰边框。这里所谓的装饰边框是指用“-”“+”“|”等字符组成的边框。下面是一个输出结果示例。

+-----------+
|Hello,world.|
+-----------+

2.1 类之间的关系

类的功能:

类图:

2.2 Display类

Display类是可以显示多行字符串的抽象类。

show是显示所有行的字符串的方法。在show方法内部,程序会调用getRows方法获取行数,调用getRowText获取该行需要显示的字符串,然后通过for循环语句将所有的字符串显示出来。show方法使用了getRows和 getRowText等抽象方法,这属于Tempate Method模式

想要了解Tempate Method模式的朋友可以看我的文章:设计模式学习(六):Template Method模板方法模式

public abstract class Display {
    /**
     * 获取横向字符数
     */
    public abstract int getColumns();

    /**
     * 获取纵向行数
     */
    public abstract int getRows();

    /**
     * 获取第row行的字符串
     */
    public abstract String getRowText(int row);

    /**
     * 全部显示
     */
    public final void show() {
        for (int i = 0; i < getRows(); i++) {
            System.out.println(getRowText(i));
        }
    }
}

2.3 StringDisplay类

仅查看Display类的代码是不能明白程序究竟要做什么的。下面我们来看看它的子类——StringDisplay类。

StringDisplay类是用于显示单行字符串的类。由于StringDisplay类是Display类的子类,因此它肩负着实现Display类中声明的抽象方法的重任。

为了简单起见,这里我们以内存上的一字节长度的字符占界面上的一列为前提。

此外,仅当要获取第0行的内容时getRowText方法才会返回string字段。以本章开头的蛋糕的比喻来说,StringDisplay类就相当于生日蛋糕中的核心蛋糕。

public class StringDisplay extends Display{
    //要显示的字符串
    private String string;

    public StringDisplay(String string) {
        this.string = string;
    }

    /**
     * @return 字符数
     */
    @Override
    public int getColumns() {
        return string.getBytes().length;
    }

    /**
     * 行数是 1
     */
    @Override
    public int getRows() {
        return 1;
    }

    /**
     * 仅当row为0时返回值
     */
    @Override
    public String getRowText(int row) {
        if (row == 0) {
            return string;
        } else {
            return null;
        }
    }
}

2.4 Border类

Border类是装饰边框的抽象类。虽然它所表示的是装饰边框,但它也是Display类的子类。

也就是说,通过继承,装饰边框与被装饰物具有了相同的方法。具体而言,Border类继承了父类的getcolumns、getRows、getRowText、show等各方法。从接口(API)角度而言,装饰边框(Border )与被装饰物( Display)具有相同的方法也就意味着它们具有一致性。

在装饰边框Border类中有一个 Display类型的display字段,它表示被装饰物。不过,display字段所表示的被装饰物并仅不限于StringDisplay的实例。因为,Border也是Display类的子类,display字段所表示的也可能是其他的装饰边框(Border类的子类的实例),而且那个边框中也有一个display字段。这样,大家应该能大致理解Decorator模式的结构了吧。

public abstract class Border extends Display {
    //表示被装饰物
    protected Display display;

    protected Border(Display display) {
        this.display = display;
    }
}

2.5 SideBorder类

SideBorder类是一种具体的装饰边框,是Border类的子类。SideBorder类用指定的字符(borderchar)装饰字符串的左右两侧。例如,如果指定borderchar字段的值是“|”,那么我们就可以调用show方法,像下面这样在“被装饰物”的两侧加上“”。还可以通过构造函数指定borderchar字段。

|被装饰物|

SideBorder类并非抽象类,这是因为它实现了父类中声明的所有抽象方法。

display字段的可见性是protected,因此sideBorder类的子类都可以使用该字段。

public class SideBorder extends Border{

    // 表示装饰边框的字符
    private char borderChar;

    /**
     * @param display 被装饰字符串
     * @param ch 装饰边框的字符
     */
    protected SideBorder(Display display, char ch) {
        super(display);
        this.borderChar = ch;
    }

    @Override
    public int getColumns() {
        return 1 + display.getColumns() + 1;
    }

    @Override
    public int getRows() {
        return display.getRows();
    }

    @Override
    public String getRowText(int row) {
        // 被装饰物的字符串 加上 两侧边框字符
        return borderChar + display.getRowText(row) + borderChar;
    }
}

2.6 FullBorder类

FullBorder类与SideBorder类一样,也是Border类的子类。SideBorder类会在字符串的左右两侧加上装饰边框,而FullBorder类则会在字符串的上下左右都加上装饰边框。不过,在SideBorder类中可以指定边框的字符,而在FullBorder类中,边框的字符是固定的。

public class FullBorder extends Border {
    public FullBorder(Display display) {
        super(display);
    }

    @Override
    public int getColumns() {
        return 1 + display.getColumns() + 1;
    }

    @Override
    public int getRows() {
        return 1 + display.getRows() + 1;
    }

    @Override
    public 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) + "|";
        }
    }

    /**
     * 连续地显示某个指定字符
     * @param ch 指定的显示字符
     * @param count 显示次数
     * @return 显示出的字符
     */
    private String makeLine(char ch, int count) {
        StringBuffer buf = new StringBuffer();
        for (int i = 0; i < count; i++) {
            buf.append(ch);
        }
        return buf.toString();
    }
}

2.7 Main类

Main类是用于测试程序行为的类。在Main类中一共生成了4个实例,即b1~b4,它们的作用分别是:

  • b1:将"Hello, world.”不加装饰地直接显示出来

  • b2:在b1的两侧加上装饰边框'#'

  • b3:在b2的上下左右加上装饰边框

  • b4:为"你好,世界。"加上多重边框

public class Main {
    public static void main(String[] args) {
        // b1:将"Hello, world.”不加装饰地直接显示出来    
        Display b1 = new StringDisplay("Hello, world");
        // b2:在b1的两侧加上装饰边框'#'
        Display b2 = new SideBorder(b1, '#');
        // b3:在b2的上下左右加上装饰边框
        Display b3 = new FullBorder(b2);
        b1.show();
        b2.show();
        b3.show();
        // b4:为"你好,世界。"加上多重边框
        Display b4 = new SideBorder(
                        new FullBorder(
                                new FullBorder(
                                        new SideBorder(
                                                new FullBorder(
                                                        new StringDisplay("你好,世界。")
                                                ),'*'
                                        )
                                )
                        ),'/');
        b4.show();
    }
}

2.8 运行结果

b3、b2、b1对象图:

三、拓展思路的要点

3.1 接口(API)的透明性

在Decorator模式中,装饰边框与被装饰物具有一致性。具体而言,在示例程序中,表示装饰边框的Border类是表示被装饰物的Display类的子类,这就体现了它们之间的一致性。也就是说,Border类(以及它的子类)与表示被装饰物的Display类具有相同的接口(API )。

这样,即使被装饰物被边框装饰起来了,接口(API)也不会被隐藏起来。其他类依然可以调用getcolumns、getRows、getRowText 以及 show方法。这就是接口(API )的“透明性”。

在示例程序中,实例b4被装饰了多次,但是接口(API)却没有发生任何变化。

这得益于接口(API )的透明性,Decorator模式中也形成了类似于Composite模式中的递归结构。也就是说,装饰边框里面的“被装饰物”实际上又是别的物体的“装饰边框”。就像是剥洋葱时以为洋葱心要出来了,结果却发现还是皮。不过,Decorator模式虽然与Composite模式一样,都具有递归结构,但是它们的使用目的不同。Decorator模式的主要目的是通过添加装饰物来增加对象的功能。

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

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

Decorator模式使用了委托。对“装饰边框”提出的要求(调用装饰边框的方法)会被转交(委托)给“被装饰物”去处理。以示例程序来说,就是SideBorder类的getColumns方法调用了display.getColumns ()。除此以外,getRows方法也调用了display.getRows()。

3.3 可以动态地增加功能

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

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

使用Decorator模式可以为程序添加许多功能。只要准备一些装饰边框(ConcreteDecorator角色),即使这些装饰边框都只具有非常简单的功能,也可以将它们自由组合成为新的对象。

这就像我们可以自由选择香草味冰激凌、巧克力冰激凌、草莓冰激凌、猕猴桃冰激凌等各种口味的冰激凌一样。如果冰激凌店要为顾客准备所有的冰激凌成品那真是太麻烦了。因此,冰激凌店只会准备各种香料,当顾客下单后只需要在冰激凌上加上各种香料就可以了。不管是香草味,还是咖啡朗姆和开心果的混合口味,亦或是香草味、草莓味和猕猴桃三重口味,顾客想吃什么口味都可以。Decorator模式就是可以应对这种多功能对象的需求的一种模式。

四、相关的设计模式

4.1 Adapter模式

Decorator模式可以在不改变被装饰物的接口(API)的前提下,为被装饰物添加边框(透明性)。

Adapter模式用于适配两个不同的接口(API )。

设计模式学习(三):Adapter适配器模式

4.2 Stragety模式

Decorator模式可以像改变被装饰物的边框或是为被装饰物添加多重边框那样,来增加类的功能。

Stragety模式通过整体地替换算法来改变类的功能。

设计模式学习(四):Strategy策略模式

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/177540.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JavaEE5-Spring更简单的读取和存储对象

目录 1.存储Bean对象 1.1.前置工作&#xff1a;在配置文件中设置bean扫描的根路径&#xff08;重要&#xff09; 1.2.添加注解存储Bean对象到Spring中 1.2.1.类注解(添加到某个类上&#xff0c;将当前的类存储到Spring中)&#xff1a;Controller&#xff0c;Service&#x…

树,堆,二叉树的认识

1.树概念及结构 1.1树的概念 注意&#xff1a;树形结构中&#xff0c;子树之间不能有交集&#xff0c;否则就不是树形结构 1.2 树的相关概念 1.3 树的表示 树结构相对线性表就比较复杂了&#xff0c;要存储表示起来就比较麻烦了&#xff0c;既然保存值域&#xff0c;也要保存…

Gateway服务网关

Gateway服务网关一、网关介绍二、gateway快速入门1.创建gateway服务&#xff0c;引入依赖2.编写启动类3.编写基础配置和路由规则4.重启测试5.网关路由的流程图三、断言工厂四、过滤器工厂1.路由过滤器的种类2.请求头过滤器3.默认过滤器4.总结五、全局过滤器1.全局过滤器作用2.自…

fpga实操训练(系统开发和硬件接口)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】 相信很多学习fpga的同学都会有这样的一个感受,一开始fpga学习还比较简单,但是一旦涉及到呼吸灯、uart、spi、iic、ddr2后面就会越来越难。遇到这样的困难之后,学习的激情一下子少…

从零搭建一个组件库(一)项目环境搭建

文章目录前言monorepo架构1.monorepo架构的优势2.使用pnpm搭建monorepo架构&#xff08;1&#xff09;全局安装pnpm&#xff08;2&#xff09;初始化项目&#xff08;3&#xff09;新建workspace.yaml文件4.不同包之间的相互引用TypeScript支持1.安装TypeScript2.初始化TypeScr…

http三次握手四次挥手详解

1、 TCP的三次握手和四次挥手实质就是TCP通信的连接和断开。 三次握手&#xff1a;为了对每次发送的数据量进行跟踪与协商&#xff0c;确保数据段的发送和接收同步&#xff0c;根据所接收到的数据量而确认数据发送、接收完毕后何时撤消联系&#xff0c;并建立虚连接。 四次挥…

C++6:STL-模拟实现string

string时STL中的模板库之一&#xff0c;类似于专门处理字符串的数据结构&#xff0c;在模拟实现并探讨其中构造的巧妙之处之前&#xff0c;我们短浅的认识一下STL是什么 目录 什么是STL STL的诞生 关于string string的模拟实现 构造函数和析构函数 实现简单的string打印 …

【蓝桥杯】简单数论2——快速幂矩阵快速幂

1、快速幂 1.1运算模 定义&#xff1a;模运算为a除以m的余数&#xff0c;记为a mod m&#xff0c;有a mod m a % m。 模运算是大数运算中的常用操作&#xff1a;如果一个数太大&#xff0c;无法直接输出&#xff0c;或者不需要直接输出&#xff0c;可以把它取模后&#xff0…

Android 深入系统完全讲解(37)

7.5 源码讲解 dlopen 打开动态库 dlsym 找到符号 (*print_func)(); 调用方法 我们可以看到&#xff0c;要使用一个 so 库的某个方法&#xff0c;就上面三步骤&#xff0c;加载 &#xff0c;查找 &#xff0c;使用 。我 们这里调用了 so 库中的 my_print 方法。 7.6 运行 我们把…

Linux——进程间通信

文章目录前言1. 进程间通信方式的一些标准&#xff1a;2. 管道2.1 什么是管道2.2 管道的原理2.3 匿名管道2.3.1 实例代码1. demo代码2. 总结管道的特点&#xff0c;理解以前的管道 |3. 扩展——进程池2.4 管道读写规则2.5 命名管道2.5.1 创建一个命名管道2.5.2 命名管道的打开规…

Python break用法详解

我们知道&#xff0c;在执行 while 循环或者 for 循环时&#xff0c;只要循环条件满足&#xff0c;程序将会一直执行循环体&#xff0c;不停地转圈。但在某些场景&#xff0c;我们可能希望在循环结束前就强制结束循环&#xff0c;Python 提供了 2 种强制离开当前循环体的办法&a…

路由处理及功能(实现了权限控制vue admin)

界面简化 将 template 改为&#xff1a; <template><div class"login-container"><el-formref"loginForm":model"loginForm":rules"loginRules"class"login-form"autocomplete"on"label-positio…

Mybatis遇到的脑残问题

一、MySQL的版本问题 有的教程mysql是8.0版本使用jar包不一样 <dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0</version></dependency>然后我查了一下我的mysql版本是5.7版…

分支语句与循环语句

文章目录 什么是语句&#xff1f; 分支语句&#xff08;选择结构&#xff09;循环语句goto语句前言 一、什么是语句&#xff1f; C语句可分为以下五类&#xff1a; 1. 表达式语句 2. 函数调用语句 3. 控制语句 4. 复合语句 5. 空语句 控制语句用于控制程序的执行流程&#xff0…

第九层(1):初识STL

文章目录前情回顾初识STLSTL的诞生STL的基本概念STL六大组件STL中的容器、算法、迭代器容器算法迭代器容器、算法、迭代器的配合使用vector中的嵌套使用石碑倒下...后面还有石碑&#xff1f;本章知识点&#xff08;图片形式&#xff09;&#x1f389;welcome&#x1f389; ✒️…

为什么带NOLOCK的查询语句还会造成阻塞

背景客户反映HIS数据库在11点出现了长时间的阻塞&#xff0c;直到手动KILL掉阻塞的源头。请我们协助分析原因&#xff0c;最终定位到.NET程序中使用的SqlDataReader未正常关闭导致。现象登录SQL专家云&#xff0c;进入趋势分析&#xff0c;在活动会话中回溯11点一个小时内的运行…

【Ajax】防抖和节流

一、防抖什么是防抖防抖策略&#xff08;debounce&#xff09;是当事件被触发后&#xff0c;延迟 n 秒后再执行回调&#xff0c;如果在这 n 秒内事件又被触发&#xff0c;则重新计时。如果事件被频繁触发&#xff0c;防抖能保证只有最有一次触发生效&#xff01;前面 N 多次的触…

【Linux IO】文件描述符、重定向、缓冲区

1.open函数1.1第二个参数的解释&#xff1b;O_RDONLY: 只读打开 O_WRONLY: 只写打开 O_RDWR : 读&#xff0c;写打开上面三个常量&#xff0c;必须指定一个且只能指定一个 O_CREAT : 若文件不存在&#xff0c;则创建它。需要使用mode选项&#xff0c;来指明新文件的访问权限 O_…

MyBatis 连接数据库与增删改查

❤️作者主页&#xff1a;微凉秋意 ✅作者简介&#xff1a;后端领域优质创作者&#x1f3c6;&#xff0c;CSDN内容合伙人&#x1f3c6;&#xff0c;阿里云专家博主&#x1f3c6; ✨精品专栏&#xff1a;数据结构与课程设计 &#x1f525;系列专栏&#xff1a;javaweb 文章目录前…

C++设计模式(8)——命令模式

命令模式 亦称&#xff1a;动作、事务、Action、Transaction、Command 意图 命令模式是一种行为设计模式&#xff0c; 它可将请求转换为一个包含与请求相关的所有信息的独立对象。 该转换让你能根据不同的请求将方法参数化、 延迟请求执行或将其放入队列中&#xff0c; 且能…