设计模式:装饰模式(Decorator)

2024-05-29 06:12

本文主要是介绍设计模式:装饰模式(Decorator),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

设计模式:装饰模式(Decorator)

  • 设计模式:装饰模式(Decorator)
    • 模式动机
    • 模式定义
    • 模式结构
    • 时序图
    • 模式实现
    • 在单线程环境下的测试
    • 在多线程环境下的测试
    • 模式分析
    • 优缺点
    • 适用场景
    • 应用场景
    • 应用实例
    • 模式扩展
    • 参考

设计模式:装饰模式(Decorator)

装饰模式(Decorator)属于结构型模式(Structural Pattern)的一种。

结构型模式(Structural Pattern)描述如何将类或者对象结合在一起形成更大的结构,就像搭积木,可以通过简单积木的组合形成复杂的、功能更为强大的结构。

结构型模式可以分为类结构型模式和对象结构型模式:

  • 类结构型模式关心类的组合,由多个类可以组合成一个更大的系统,在类结构型模式中一般只存在继承关系和实现关系。
  • 对象结构型模式关心类与对象的组合,通过关联关系使得在一个类中定义另一个类的实例对象,然后通过该对象调用其方法。根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系,因此大部分结构型模式都是对象结构型模式。

模式动机

一般有两种方式可以实现给一个类或对象增加行为:

  • 继承机制:使用继承机制是给现有类添加功能的一种有效途径,通过继承一个现有类可以使得子类在拥有自身方法的同时还拥有父类的方法。但是这种方法是静态的,用户不能控制增加行为的方式和时机。
  • 关联机制:即将一个类的对象嵌入另一个对象中,由另一个对象来决定是否调用嵌入对象的行为以便扩展自己的行为,我们称这个嵌入的对象为装饰器(Decorator)。

装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任,换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰模式可以在不需要创造更多子类的情况下,将对象的功能加以扩展。这就是装饰模式的模式动机。

模式定义

装饰模式(Decorator)别名为油漆工模式,属于对象结构型模式。

装饰模式允许向一个现有的对象添加新的功能,同时又不改变其结构。就增加对象功能来说,装饰模式比生成子类实现更为灵活。

模式结构

装饰模式(Decorator)包含如下角色:

  • 抽象组件(Component):定义了原始对象和装饰器对象的公共接口或抽象类,可以是具体组件类的父类或接口。
  • 具体组件(Concrete Component):是被装饰的原始对象,它定义了需要添加新功能的对象。
  • 抽象装饰器(Decorator):继承自抽象组件,它包含了一个抽象组件对象,并定义了与抽象组件相同的接口,同时可以通过组合方式持有其他装饰器对象。
  • 具体装饰器(Concrete Decorator):实现了抽象装饰器的接口,负责向抽象组件添加新的功能。具体装饰器通常会在调用原始对象的方法之前或之后执行自己的操作。

在这里插入图片描述

装饰器模式通过嵌套包装多个装饰器对象,可以实现多层次的功能增强。每个具体装饰器类都可以选择性地增加新的功能,同时保持对象接口的一致性。

时序图

在这里插入图片描述

模式实现

抽象组件 Component.h:

#ifndef _COMPONENT_H_
#define _COMPONENT_H_class Component
{
public:virtual void operation() = 0;
};#endif // !_COMPONENT_H_

具体组件 ConcreteComponent.h:

#ifndef _CONCRETE_COMPONENT_H_
#define _CONCRETE_COMPONENT_H_#include <iostream>#include "Component.h"class ConcreteComponent : public Component
{
public:virtual void operation(){std::cout << "ConcreteComponent's normal operation." << std::endl;}
};#endif // !_CONCRETE_COMPONENT_H_

抽象装饰器 Decorator.h:

#ifndef _DECORATOR_H_
#define _DECORATOR_H_#include "Component.h"class Decorator : public Component
{
public:virtual void operation() = 0;
};#endif // !_DECORATOR_H_

具体装饰器:

ConcreteDecoratorA.h:

#ifndef _CONCRETE_DECORATOR_A_H_
#define _CONCRETE_DECORATOR_A_H_#include <iostream>#include "Decorator.h"class ConcreteDecoratorA : public Decorator
{
private:Component* m_pComponent;public:ConcreteDecoratorA(Component* pComponent) : m_pComponent(pComponent) {};virtual ~ConcreteDecoratorA(){delete m_pComponent;}void addBehavior(){std::cout << "Add behavior A." << std::endl;}virtual void operation(){m_pComponent->operation();addBehavior();}
};#endif // !_CONCRETE_DECORATOR_A_H_

ConcreteDecoratorB.h:

#ifndef _CONCRETE_DECORATOR_B_H_
#define _CONCRETE_DECORATOR_B_H_#include <iostream>#include "Decorator.h"class ConcreteDecoratorB : public Decorator
{
private:Component* m_pComponent;public:ConcreteDecoratorB(Component* pComponent) : m_pComponent(pComponent) {};virtual ~ConcreteDecoratorB(){delete m_pComponent;}void addBehavior(){std::cout << "Add behavior B." << std::endl;}virtual void operation(){m_pComponent->operation();addBehavior();}
};#endif // !_CONCRETE_DECORATOR_A_H_

在单线程环境下的测试

测试代码:

#include <stdlib.h>#include "ConcreteComponent.h"
#include "ConcreteDecoratorA.h"
#include "ConcreteDecoratorB.h"int main()
{Component* component = new ConcreteComponent();Decorator* decorator1 = new ConcreteDecoratorA(component);Decorator* decorator2 = new ConcreteDecoratorB(decorator1);decorator2->operation();delete component;delete decorator1;delete decorator2;system("pause");return 0;
}

运行结果:

在这里插入图片描述

在多线程环境下的测试

略。

模式分析

  • 与继承关系相比,关联关系的主要优势在于不会破坏类的封装性,而且继承是一种耦合度较大的静态关系,无法在程序运行时动态扩展。在软件开发阶段,关联关系虽然不会比继承关系减少编码量,但是到了软件维护阶段,由于关联关系使系统具有较好的松耦合性,因此使得系统更加容易维护。当然,关联关系的缺点是比继承关系要创建更多的对象。
  • 使用装饰模式来实现扩展比继承更加灵活,它以对客户透明的方式动态地给一个对象附加更多的责任。装饰模式可以在不需要创造更多子类的情况下,将对象的功能加以扩展。

优缺点

优点:

  1. 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。
  2. 可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的装饰器,从而实现不同的行为。
  3. 通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。可以使用多个具体装饰类来装饰同一对象,得到功能更为强大的对象。
  4. 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,在使用时再对其进行组合,原有代码无须改变,符合“开闭原则”。

缺点:

  1. 使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同,同时还将产生很多具体装饰类。这些装饰类和小对象的产生将增加系统的复杂度,加大学习与理解的难度。
  2. 装饰模式比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为繁琐。

适用场景

在以下情况下可以使用装饰模式(Decorator):

  1. 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
  2. 需要动态地给一个对象增加功能,这些功能也可以动态地被撤销。
  3. 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。

不能采用继承的情况主要有两类:
第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长;
第二类是因为类定义不能继承(如final类)。

应用场景

  1. IO流的处理:这是一个典型的装饰者模式的应用。InputSteam和OutputStream是最基本的抽象组件(Component),而各种FilterInputSteam和FilterOutputStream就是具体的装饰器,它们可以实现各种不同的IO流处理功能,如缓冲、压缩、加密等等。
  2. 数据库连接池:连接池为抽象组件(Component),各种不同的连接池实现则是具体的装饰器(Concrete Decorator),它们可以实现不同的连接池缓存策略、连接池大小、超时时间等属性。

应用实例

下面以英雄联盟中李青(盲僧)学技能为例。其中Hero代表英雄(抽象构件)、BlindMonk(盲僧-具体构件)、SkillDecorator(技能-抽象装饰器)、QDecorator、WDecorator、EDecorator、RDecorator为英雄的四个技能(具体装饰器)。

UML 类图:

在这里插入图片描述

完整代码:

#include <iostream>
#include <string>
#include <stdlib.h>
using namespace std;class Hero
{
public:virtual void learnSkill() = 0;
};class BlindMonk : public Hero
{
private:string heroName;public:BlindMonk(string name) : heroName(name){};virtual void learnSkill(){cout << "英雄名称:" << heroName << endl;}
};class SkillDecorator : public Hero
{
public:virtual void learnSkill() = 0;
};class QDecorator : public SkillDecorator
{
private:Hero *hero;string skillName;public:QDecorator(Hero *pHero, string name) : hero(pHero), skillName(name){};~QDecorator(){delete hero;}void learnQ(){cout << "习得技能:" << skillName << endl;}virtual void learnSkill(){hero->learnSkill();learnQ();}
};class WDecorator : public SkillDecorator
{
private:Hero *hero;string skillName;public:WDecorator(Hero *pHero, string name) : hero(pHero), skillName(name){};~WDecorator(){delete hero;}void learnW(){cout << "习得技能:" << skillName << endl;}virtual void learnSkill(){hero->learnSkill();learnW();}
};class EDecorator : public SkillDecorator
{
private:Hero *hero;string skillName;public:EDecorator(Hero *pHero, string name) : hero(pHero), skillName(name){};~EDecorator(){delete hero;}void learnE(){cout << "习得技能:" << skillName << endl;}virtual void learnSkill(){hero->learnSkill();learnE();}
};class RDecorator : public SkillDecorator
{
private:Hero *hero;string skillName;public:RDecorator(Hero *pHero, string name) : hero(pHero), skillName(name){};~RDecorator(){delete hero;}void learnR(){cout << "习得终极技能:" << skillName << endl;}virtual void learnSkill(){hero->learnSkill();learnR();}
};int main()
{Hero *hero = new BlindMonk("李青");SkillDecorator *q = new QDecorator(hero, "Q:天音波/回音击");SkillDecorator *w = new WDecorator(q, "W:金钟罩/铁布衫");SkillDecorator *e = new EDecorator(w, "E:天雷破/摧筋断骨");SkillDecorator *r = new RDecorator(e, "R:猛龙摆尾");r->learnSkill();system("pause");return 0;
}

运行结果:

在这里插入图片描述

模式扩展

装饰模式的简化:如果只有一个具体构件类而没有抽象构件类,那么抽象装饰类可以作为具体构件类的直接子类。

一个装饰类的接口必须与被装饰类的接口保持相同,对于客户端来说无论是装饰之前的对象还是装饰之后的对象都可以一致对待。

参考

  1. https://design-patterns.readthedocs.io/zh-cn/latest/structural_patterns/decorator.html
  2. https://www.runoob.com/design-pattern/decorator-pattern.html
  3. https://blog.csdn.net/weixin_45433817/article/details/131037102
  4. https://blog.csdn.net/weixin_45433817/article/details/131074125

这篇关于设计模式:装饰模式(Decorator)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1012870

相关文章

Redis高可用-主从复制、哨兵模式与集群模式详解

《Redis高可用-主从复制、哨兵模式与集群模式详解》:本文主要介绍Redis高可用-主从复制、哨兵模式与集群模式的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝... 目录Redis高可用-主从复制、哨兵模式与集群模式概要一、主从复制(Master-Slave Repli

一文带你搞懂Redis Stream的6种消息处理模式

《一文带你搞懂RedisStream的6种消息处理模式》Redis5.0版本引入的Stream数据类型,为Redis生态带来了强大而灵活的消息队列功能,本文将为大家详细介绍RedisStream的6... 目录1. 简单消费模式(Simple Consumption)基本概念核心命令实现示例使用场景优缺点2

Python装饰器之类装饰器详解

《Python装饰器之类装饰器详解》本文将详细介绍Python中类装饰器的概念、使用方法以及应用场景,并通过一个综合详细的例子展示如何使用类装饰器,希望对大家有所帮助,如有错误或未考虑完全的地方,望不... 目录1. 引言2. 装饰器的基本概念2.1. 函数装饰器复习2.2 类装饰器的定义和使用3. 类装饰

Nginx location匹配模式与规则详解

《Nginxlocation匹配模式与规则详解》:本文主要介绍Nginxlocation匹配模式与规则,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、环境二、匹配模式1. 精准模式2. 前缀模式(不继续匹配正则)3. 前缀模式(继续匹配正则)4. 正则模式(大

Linux系统配置NAT网络模式的详细步骤(附图文)

《Linux系统配置NAT网络模式的详细步骤(附图文)》本文详细指导如何在VMware环境下配置NAT网络模式,包括设置主机和虚拟机的IP地址、网关,以及针对Linux和Windows系统的具体步骤,... 目录一、配置NAT网络模式二、设置虚拟机交换机网关2.1 打开虚拟机2.2 管理员授权2.3 设置子

SpringBoot如何通过Map实现策略模式

《SpringBoot如何通过Map实现策略模式》策略模式是一种行为设计模式,它允许在运行时选择算法的行为,在Spring框架中,我们可以利用@Resource注解和Map集合来优雅地实现策略模式,这... 目录前言底层机制解析Spring的集合类型自动装配@Resource注解的行为实现原理使用直接使用M

C#原型模式之如何通过克隆对象来优化创建过程

《C#原型模式之如何通过克隆对象来优化创建过程》原型模式是一种创建型设计模式,通过克隆现有对象来创建新对象,避免重复的创建成本和复杂的初始化过程,它适用于对象创建过程复杂、需要大量相似对象或避免重复初... 目录什么是原型模式?原型模式的工作原理C#中如何实现原型模式?1. 定义原型接口2. 实现原型接口3

大数据spark3.5安装部署之local模式详解

《大数据spark3.5安装部署之local模式详解》本文介绍了如何在本地模式下安装和配置Spark,并展示了如何使用SparkShell进行基本的数据处理操作,同时,还介绍了如何通过Spark-su... 目录下载上传解压配置jdk解压配置环境变量启动查看交互操作命令行提交应用spark,一个数据处理框架

Java实现状态模式的示例代码

《Java实现状态模式的示例代码》状态模式是一种行为型设计模式,允许对象根据其内部状态改变行为,本文主要介绍了Java实现状态模式的示例代码,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来... 目录一、简介1、定义2、状态模式的结构二、Java实现案例1、电灯开关状态案例2、番茄工作法状态案例

在JS中的设计模式的单例模式、策略模式、代理模式、原型模式浅讲

1. 单例模式(Singleton Pattern) 确保一个类只有一个实例,并提供一个全局访问点。 示例代码: class Singleton {constructor() {if (Singleton.instance) {return Singleton.instance;}Singleton.instance = this;this.data = [];}addData(value)