当前位置: 首页 > news >正文

设计模式之装饰器模式

面向对象设计原则

接口隔离原则:面向对象设计之接口隔离原则-CSDN博客

设计模式

工厂模式 : 设计模式之工厂模式-CSDN博客

迭代器模式:设计模式之迭代器模式-CSDN博客

适配器模式:设计模式之适配器模式-CSDN博客

过滤器模式:设计模式之过滤器模式-CSDN博客

单例模式:设计模式之单例模式-CSDN博客

观察者模式:设计模式之观察者模式-CSDN博客

空对象模式:设计模式之空对象模式-CSDN博客

目录

1.概述

2.结构

3.实现

3.1.示例1:简单实现

3.2.示例2:函数装饰器

4.总结


1.概述

        装饰器模式也是我们日常编程用的比较多的一种设计模式,它允许向一个现有的对象添加新的功能,同时又不改变其结构,实际上就是现有的类的一个包装。这种模式可以视为结构性模式,应为它提供了一种用于组合类的机制,,使得这些类可以在不改变其代码的情况下进行扩展。

        在装饰器模式中,有一个被装饰的组件(通常是抽象类或接口),它定义了需要实现的方法。然后,有一个装饰器类,它实现了同样的接口,并且包装了被装饰的组件。装饰器类可以在被装饰的组件方式调用之前或之后添加额外的功能。

2.结构

装饰器模式的UML类图如下:

主要角色:

1)抽象组件(Component):定义了原始对象和装饰器对象的公共接口或抽象类,可以是具体组件类的父类或接口。

2)具体组件(Concrete Component):是被装饰的原始对象,它定义了需要添加新功能的对象。

3)抽象装饰器(Decorator):这是一个抽象基类或接口,它也实现了组件接口,并包含一个成员变量用来引用具体组件对象。装饰器可以添加额外的功能,通常通过在装饰器中委托给具体组件实现。

4)具体装饰器(Concrete Decorator):实现了抽象装饰器的接口,负责向抽象组件添加新的功能。具体装饰器通常会在调用原始对象的方法之前或之后执行自己的操作。

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

        在C++中,装饰器模式通常涉及到类的继承和组合,以及对虚函数的使用,以确保适当的方法可以在组合中被调用。这使得C++的装饰器模式非常灵活,可用于构建可维护和可扩展的对象结构。

3.实现

3.1.示例1:简单实现

Decorator.h

#include <iostream>
#include <memory>// 抽象组件
class Component {
public:virtual void doWork() = 0;
};// 具体组件
class ConcreteComponent : public Component {
public:void doWork() override {// 执行具体操作std::cout << "ConcreteComponent doWork..." << std::endl;}
};// 抽象装饰器
class Decorator : public Component {
protected:Component* m_pComponent;
public:Decorator(Component* pComponent) : m_pComponent(pComponent) {}void doWork() override {m_pComponent->doWork();}
};// 具体装饰器
class ConcreteDecorator1 : public Decorator {
public:ConcreteDecorator1(Component* pComponent) : Decorator(pComponent) {}void doWork() override {std::cout << "ConcreteDecorator1 doWork before..." << std::endl;__super::doWork();std::cout << "ConcreteDecorator1 doWork after..." << std::endl;}
};// 具体装饰器
class ConcreteDecorator2 : public Decorator {
public:ConcreteDecorator2(Component* pComponent) : Decorator(pComponent) {}void doWork() override {std::cout << "ConcreteDecorator2 doWork before..." << std::endl;__super::doWork();std::cout << "ConcreteDecorator2 doWork after..." << std::endl;}
};

main.cpp

int main()
{std::unique_ptr<Component> pConcreteComponent(new ConcreteComponent());std::unique_ptr<Component> pConcreteDecorator1(new                     ConcreteDecorator1(pConcreteComponent.get()));std::unique_ptr<Component> pConcreteDecorator2(new ConcreteDecorator2(pConcreteDecorator1.get()));pConcreteComponent->doWork();std::cout << std::endl;pConcreteDecorator1->doWork();std::cout << std::endl;pConcreteDecorator2->doWork();
}

输出:

ConcreteComponent doWork...ConcreteDecorator1 doWork before...
ConcreteComponent doWork...
ConcreteDecorator1 doWork after...ConcreteDecorator2 doWork before...
ConcreteDecorator1 doWork before...
ConcreteComponent doWork...
ConcreteDecorator1 doWork after...
ConcreteDecorator2 doWork after...

pConcreteComponent和pConcreteDecorator1还比较简单,pConcreteDecorator2装饰器就比较复杂了,就好像是在pConcreteDecorator1的基础上套了一个壳,从这点也可以看出装饰器模式结构可以非常的灵活。

3.2.示例2:函数装饰器

        虽然装饰器模式通常用于类,但是同样可以应用于函数。例如,假设代码中有一个特定的函数给你带来了麻烦:你希望记录该函数被调用的所有时间,并在Excel中分析统计信息。当然,这可以通过在调用函数前后放置一些代码来实现,即

cout<< "Entering function XYz\n":
// do the work
cout <<"Exiting function XYZ\n";

        这很好,但从关注点分离的角度来看并不好:我们确实希望将日志功能存储在某个地方,以便复用并在必要时强化它。
        有不同的方法可以实现这一点。一种方法是简单地将整个工作单元作为一个函数提供给某些日志组件:

struct Logger{function<void()> func;string name;
public:Logger(const function<void()>& func, const string& name):func{func},name{name}{}void operator()()const {cout<< "Entering "<< name << "\n";func();cout<< "Exiting "<< name << "\n";}
};

使用这种方法,我们可以编写如下代码来使用它:

Logger([](){ cout << "Hello\n";},“HelloFunction")();// Entering HelloFunction
// Hello
// Exiting HelloFunction

始终有一种选择,可以将函数作为模板参数而不是std::function传人。这导致与前面的结果略有不同:

template <typename Func>
struct Logger2{Func func;string name;
public:Logger2(const Func& func,const string& name):func{func},name{name}{}void operator()()const{cout << "Entering "<< name << endl;func();cout << "Exiting "<< name << endl;}
};

此实现的用途完全不变。我们可以创建一个工具函数来实际创建这样的记录器:

template <typename Func> 
auto make_logger2(Func func,const string& name)
{return logger2<Func>{ func, name };
}

然后像下面这样使用它:

auto call = make_logger2([](){cout <<"Hello!" <<endl; },"HelloFunction");
call();// output same as before

你可能会问 "这有什么意义?" 嗯……我们现在有能力在需要装饰某个函数时创建一个装饰器(其中包含装饰的函数)并调用它。
现在,我们遇到了一个新的挑战:如果要记录调用函数add()的日志,该怎么办呢?此处将add()函数定义如下:

double add(double a, double b)
{cout << a << "+"<< b<<"="<<(a + b)<< endl;return a + b;
}

需要 add()函数的返回值吗?如果需要的话,它将从 1ogger中返回(因为1ogger装饰了add()函数)。这并不容易!但也绝不是不可能。我们再次改进一下记录器:

template <typename R, typename... Args>
struct Logger3<R(Args...)>
{Logger3(function<R(Args...)>func, const string& name): func{func},name{name}{}R operator()(Args ...args){    cout << "Entering"<< name << endl;R result = func(args...);cout <<"Exiting "<< name << endl;return result;}function<R(Args ...)> func;string name;
};

在上述代码中,模板参数R表示返回值的类型,Args表示丽数的参数类型。与之前相同的是,装饰器在必要时调用函数;唯一的区别是opeIator() 返回了一个R,因此采用方法不会丢失返回值。
        我们可以构造另一个工具函数 make_:

template <typename R, typename... Args>
auto make_1ogger3(R(*func)(Args...), const string& name)
{return Logger3<R(Args...)>(function<R(Args...)>(func)name);
}

请注意,这里没有使用std::function,而是将第一个参数定义为普通函数指针,我们现在可以使用此函数实例化日志调用并使用它:

auto logged add = make logger3(add, "Add");
auto result=logged add(2,3);

当然,make_logger3可以被依赖注入取代。这种方法的好处是能够:
1)通过提供空对象而不是实际的记录器,动态地打开和关闭日志记录 。
2)禁用记录器正在记录的代码的实际调用(同样,通过替换其他记录器)。
总之,对于开发人员而言,这是一个有用的工具函数。

4.总结

使用装饰器模式的好处:

1)可以在运行时动态的添加或删除功能。

2)可以递归地组合多个装饰器,从而创建复杂的对象。

3)可以使用不同的装饰器组合来创建不同的行为。

4)可以在不修改原有代码的情况下增加新功能。

总体来说,装饰器模式是一种强大且灵活的设计模式,它可以用于创建可扩展、可定制和可组合的对象。

http://www.lryc.cn/news/286574.html

相关文章:

  • 【Java万花筒】缓存与存储:Java应用中的数据处理利器
  • 解决nodejs报错内存泄漏问题,项目无法运行
  • 计算机网络-物理层基本概念(接口特性 相关概念)
  • 从规则到神经网络:机器翻译技术的演化之路
  • python 面经
  • Ubuntu (Linux) 下创建软链接(即符号链接,相当于windows下的快捷方式)方法
  • LeetCode.2765. 最长交替子数组
  • Springboot日志框架logback与log4j2
  • 浪花 - 用户信息展示+更新
  • xxe漏洞之scms靶场漏洞
  • Unity3d C#实现三维场景中图标根据相机距离动态缩放功能
  • Linux网络编程(二-套接字)
  • 【DeepLearning-1】 注意力机制(Attention Mechanism)
  • c++:string相关的oj题(415. 字符串相加、125. 验证回文串、541. 反转字符串 II、557. 反转字符串中的单词 III)
  • HuoCMS|免费开源可商用CMS建站系统HuoCMS 2.0下载(thinkphp内核)
  • VsCode + CMake构建项目 C/C++连接Mysql数据库 | 数据库增删改查C++封装 | 信息管理系统通用代码 ---- 课程笔记
  • HackTheBox - Medium - Linux - Ransom
  • 柠檬微趣面试准备
  • uniapp嵌套webview,无法返回上一级?
  • 【优先级队列 之 堆的实现】
  • Vue中$watch()方法和watch属性的区别
  • openssl3.2 - 官方demo学习 - test - certs - 001 - Primary root: root-cert
  • 小程序商城能不能自己开发?
  • GPTBots:利用FlowBot中的卡片和表单信息,提供丰富的客服体验
  • ERC20 解读
  • C#,入门教程(31)——预处理指令的基础知识与使用方法
  • Java SE:面向对象(下)
  • 搭建开源数据库中间件MyCat2-配置mysql数据库双主双从
  • Oracle 19c rac集群管理 -------- 集群启停操作过程
  • 【Java】HttpServlet类中前后端交互三种方式(query string、form表单、JSON字符串)