C++设计模式 - 装饰模式

2022-01-18 16:00:20 浏览数 (2)

装饰模式

装饰模式指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。 --百度百科

装饰者模式主要是为一个对象增加新的行为,效果与子类扩展父类类似,但实现方式与继承不同,且更加灵活。

装饰模式类图

装饰模式装饰模式

Componet: 定义接口基类。

ConcreteComponent: 具体被装饰的目标对象。

Decorator: 装饰类基类。

ConcreteDecoratorA: 具体的装饰类A

ConcreteDecoratorB: 具体的装饰类B

应用场景

实际需求

实现一款定制日志,包括日志打印增加时间戳、能够打印到终端和文件等功能。

分析

一个最基础的日志功能是将代码调试打印输出至指定文件,现需要在此基础上增加时间戳、终端显示功能。按照以往的写法,直接修改日志功能代码,可能会引起一下问题:

  • 日志功能被引用过多,稍有不慎会引起编译问题。
  • 日志功能以趋于稳定,直接修改,可能会增添潜藏Bug。

解决方案

引入装饰模式,在原有的日志的接口上封装一层修饰代码。这样就无需修改原先的日志代码,同时保证了新需求的开发。

日志类图

日志类图日志类图

效果

客户端代码

代码语言:c 复制
int main(int argc, char *argv[])
{
    LOGI("main", "Welcome kaiyuan519!n");
    LOGD("main", "Welcome kaiyuan519!n");
    return 0;
}

终端打印

通过上图可以发现,不仅将客户端打印的字符串输出到同级目录中,同时增加了时间戳、终端显示功能。

源码

代码过长,本篇仅贴主要代码功能。可在公众号后台输入标题获取所有源码。

客户端代码

代码语言:c 复制
int main(int argc, char *argv[])
{
    LOGI("main", "Welcome kaiyuan519!n");
    LOGD("main", "Welcome kaiyuan519!n");
    return 0;
}

引用修饰类给日志增加新特性

代码语言:c 复制
// 定制log:增加时间戳、终端打印
int sys_log(int level, const char *tag, const char *msg)
{
    // 保存至文件
    if (!pLog) {
        pLog = CLog::GetInstance();
    }

    // 在终端显示
    if (!pLog2Terminal) {
        pLog2Terminal = CLog2Terminal::GetInstance(pLog);
    }

    // 增加log调试信息: 时间戳、等级、标签
    if (!pLogAddInfo) {
        pLogAddInfo = CLogAddInfo::GetInstance(pLog2Terminal);
    }

    // 完成装饰的接口
    pLogAddInfo->SetLogLevel(level);
    pLogAddInfo->WriteLog(tag, msg);

    return 0;
}

日志增加信息代码

代码语言:c 复制
int CLogAddInfo::WriteLog(const char *tag, const char *msg)
{
    char logStr[LOG_STR_LENGTH] = {0};  // 一次log打印长度不得超过2000
    int length = 0;

    // log增加调试信息: time、level、Tag: 2022-01-14 10:09:51.106 I/ <main>
    length = get_local_time(logStr);
    snprintf(logStr   length, LOG_STR_LENGTH - length, "%s <%s> %s", LEVEL_STR[levelIndex], tag, msg);

    CCustomLog::WriteLog(tag, logStr);
    return strlen(logStr);
}

输出至终端代码

代码语言:c 复制
int CLog2Terminal::WriteLog(const char *tag, const char *msg)
{
    // 输出至终端
    fprintf(stdout, "%s", msg);		// 添加新职能
    return CCustomLog::WriteLog(tag, msg);
}

总结

  • 装饰模式的实现方法,无非是将原本单一的功能,经过层层职能接口的封装,并将封装后的接口提供的客户端使用。由于职能接口与原生接口是相同的,所以客户端的使用代码无需改动。
  • 装饰模式只是改变其外表的部分,原本的功能还是保留的。
  • 在使用时,能够在不修改原有功能基础上,随意增加需要的功能。使用起来也比较灵活。
  • 缺点在于,装饰模式对初始化要求较为严格,且代码难以理解。需求增多时,会导致增加许多的类。所以在使用时,需要谨慎对待新增的职能。

0 人点赞