C++20之设计模式(19):空对象

空对象

      • 空对象
        • 场景
      • 空对象
      • 共享指针不是空对象
        • 改进设计
        • 隐式空对象
        • 总结

空对象

我们并不能总能选择自己想使用的接口。例如,我宁愿让我的车自己开车送我去目的地,而不必把100%的注意力放在道路和开车在我旁边的危险疯子身上。软件也是如此:有时你并不是真的想要某一项功能,但它是内置在接口里的。那么你会怎么做呢?创建一个空对象。

场景

假设继承了使用下列接口的库:

struct Logger
{virtual ~Logger() = default;virtual void info(const string& s) = 0;virtual void warn(const string& s) = 0;
}

这个库使用下面的接口来操作银行账户:

struct BankAccount
{std::shared_ptr<Logger> log;string name;int balance = 0;BankAccount(const std::share_ptr<Logger>& logger, const string& name, int balance):log{ logger },name{ name },balance {balance}{// more members here}
};

事实上,BankAccount可以拥有如下的成员函数:

void BankAccount::deposit(int amount)
{balance += amount;log->info(("Deposited $" + lexical_cast<string>(amount)+ " to " + name + ", balance is now $" + lexical_cast<string>(balance));
}

好了,这个实现有什么吗?如果你确实需要日志记录,也没有问题,你只需实现自己的日志记录类…

struct ConsoleLogger : Logger
{void info(const string& s) override{cout << "INFO: " << s << endl;}void warn(const string& s) override{cout << "WARNNING!!!" << s << endl;}
};

你可以直接使用它。但是,如果你根本不想要日志记录呢?

空对象

我们再来仔细看下BankAccount的构造函数

BankAccount(const shared_ptr<Logger>& logger, const string& name, int balance)

由于构造函数接受一个日志记录器,因此传递一个未初始化的shared_ptr<BankAccount>是不安全的。BankAccout可以使用指针之前,在内部检查指针是否为空,但你不知道它是否这样做了,因为没有额外的文档是不可能知道的。

因此,唯一可以传入BankAccount的是一个空对象,一个符合接口但不包含功能的类:

struct NullLoggor : Logger
{void info(const string& s) override { }void warn(const string& s) override { }
};

共享指针不是空对象

值得注意的是,shared_ptr和其他智能指针类都不是空对象。空对象是保留正确操作(执行无操作)的对象。但是,使用对未初始化的智能指针会崩溃会导致程序崩溃:

shared_ptr<int> n;
int x = *n + 1; // yikes!

值得注意的是,从调用的角度来看,没有办法使智能指针是安全的。换句话说,如果foo没有初始化,那么foo->bar()会神奇地变成一个空操作,那么你不能编写这样的智能指针。原因是前缀*和后缀->操作符只是代理了底层(原始)指针。没有办法对指针做无操作。

改进设计

停下来想一想:如果BankAccount在你的控制之下,你能改进接口使它更容易使用吗?这里有一些想法:

  • 在所有地方都进行指针检查。这就理清了BankAccount的正确性,但并没有消除库使用者的困惑。请记住,你仍然没有说明指针可以是空的。
  • 添加一个默认实参值,类似于const shared_ptr<Logger>& logger = no_logging其中no_loggingBankAccount类的某个成员。即使是这样,你仍然必须在想要使用对象的每个位置对指针值执行检查
  • 使用可选(optional)类型。它的习惯用法是正确的,并且可以传达意图,但是会导致传入一个optional<shared_ptr<T>>以及随后检查可选项是否为空。
隐式空对象

这里有一个激进的想法,需要进行两步操纵。它把涉及到把日志记录过程细分为调用(我们想要一个好的日志记录器接口)和操作(日志记录器实际做的事情)。因此,请考虑以下几点:

struct OptionalLogger : Logger 
{shared_ptr<Logger> impl;static shared_ptr<Logger> no_logging;Logger(const shared_ptr<Logger>& logger) : impl { logger } { }virtual void info(const string& s) override{if(impl) impl->info(s); // null check here}// and similar checks for other members
};// a static instance of a null object
shared_ptr<Logger> BankAccount::no_logging{};

现在我们已经从实现中抽象出了调用。我们现在要做的是像下面这样重新定义BankAccount构造函数:

shared_ptr<OptionalLogger> logger;
BankAccount(const string& name, int balance, const shared_ptr<Logger>& logger = no_logging) : log{ make_shared<OptionalLogger>(logger) },name{ name },balance{ balance } { }

如您所见,这里有一个巧妙的诡计:我们使用一个Logger,但存储一个OptionalLogger(这是代理设计模式)。然后,对这个可选记录器的所有调用都是安全的-它们只有在底层对象可用时才“发生”:

BankAccount account{ "primary account", 1000 };
account.deposit(2000); // no crash

上例中实现的代理对象本质上是Pimpl编程技法的自定义版本。

总结

空对象模式提出了一个API设计的问题:我们可以对我们所依赖的对象做什么样的假设?如果我们取一个指针(裸指针或智能指针),那么是否有义务在每次使用时检查该指针?

如果你觉得没有这种义务,那么用户实现空对象的唯一方法是构造所需接口的无操作实现,并将该实例传递进来。也就是说,这只适用于函数:例如,如果对象的字段也被使用,那么你就遇到了真正的麻烦。

如果你想主动支持空对象作为参数传递的想法,你需要明确:要么指定参数类型为std::optional,给参数一个默认值,暗示它是一个内置的空对象(例如,= no_logging),或只写文档说明什么样的值应当出现在这个位置。

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

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

相关文章

WPF MVVM使用遇见问题

一、遇见问题 1.使用Dictionary绑定ListBox的ItsSource问题 过程&#xff1a; 需要再界面动态显示字典&#xff0c;在循环中添加两条数据时&#xff0c;绑定的字典断点查看有两条&#xff0c;界面上只显示一条&#xff0c;后面再其他数量的都动态不显示&#xff0c;鼠标滚动后…

BUUCTF [安洵杯 2019]easy_serialize_php

这道题题目说easy但是对我来说极其不友好&#xff01;看了很多wp讲的模棱两可&#xff0c;我尽量来说清楚点 代码解析&#xff1a; 这里$function $_GET[f]&#xff0c;是我们通过get方式传递的&#xff0c;因为注释提示有东西先传fphpinfo看看 找到了一个东西&#xff0c;很…

每日任务:HTTP状态码详解及强缓存与协商缓存的区别

1.HTTP中常见的状态码有哪些&#xff1f; HTTP常见的状态码主要有以下几大类&#xff1a; 1XX:提示信息&#xff0c;协议处理的中间状态 2XX:请求成功 3XX:请求重定向 4XX:请求错误&#xff0c;一般是指发送请求的机器出现了问题 5XX:服务器错误&#xff0c;一般是指接受…

SQL性能优化:提升数据查询效率的秘诀

随着数据库规模的日益增大&#xff0c;SQL查询的性能问题逐渐成为开发者关注的焦点。一个低效的查询可能会导致系统响应缓慢&#xff0c;甚至崩溃。因此&#xff0c;掌握SQL性能优化的技巧对于提升系统整体性能至关重要。本文将介绍几种常用的SQL性能优化方法&#xff0c;帮助你…

IPD推行成功的核心要素(十五)项目管理提升IPD相关项目交付效率和用户体验

研发项目往往包含很多复杂的流程和具体的细节。因此&#xff0c;一套完整且标准的研发项目管理制度和流程对项目的推进至关重要。研发项目管理是成功推动创新和技术发展的关键因素。然而在实际管理中&#xff0c;研发项目管理常常面临着需求不确定、技术风险、人员素质、成本和…

STM32-寄存器DMA配置指南

配置步骤 在STM32F0xx中文参考手册中的DMA部分在开头给出了配置步骤 每个通道都可以在外设寄存器固定地址和存储器地址之间执行 DMA 传输。DMA 传输的数据 量是可编程的&#xff0c;最大达到 65535。每次传输之后相应的计数寄存器都做一次递减操作&#xff0c;直到 计数为&am…

电脑屏幕录制软件,分享4款(2024最新)

在今天&#xff0c;我们的电脑屏幕成为了一个多彩多姿的窗口。通过它我们可以浏览网页、观看视频、处理文档、进行游戏……有时&#xff0c;我们想要记录下这些精彩瞬间&#xff0c;与朋友分享&#xff0c;或者作为教程留存&#xff0c;这时&#xff0c;电脑屏幕录制就显得尤为…

HarmonyOS应用开发者高级认证,Next版本发布后最新题库 - 答案纯享版

这篇文章是高级题库答案纯享版&#xff0c;只有需要选择的选项。如果需要查看所有选项&#xff0c;可以点击下方链接跳转。此文章准确率60%~70%&#xff0c;不敢保证能过&#xff0c;仅提供参考。以考代学&#xff0c;还是推荐点击下方链接&#xff0c;查看完整的题库&#xff…

亚信安慧AntDB亮相PostgreSQL中国技术大会,获“数据库最佳应用奖”并分享数据库应用实践

7月12日&#xff0c;第13届PostgreSQL中国技术大会在杭州顺利举办&#xff0c;亚信安慧AntDB数据库荣获“数据库最佳应用奖”。大会上&#xff0c;亚信安慧AntDB数据库同事带来《基于AntDB的CRM系统全域数据库替换实践》和《亚信安慧AntDB数据库运维之路》两场精彩演讲&#xf…

【网络】网络编程套接字——UDP、TCP、UDP接口使用、TCP接口使用、UDP程序实例、TCP程序实例

文章目录 Linux网络1. UDP1.1 UDP接口使用1.1 UDP程序实例 2. TCP2.1 TCP接口使用2.2 TCP程序实例 Linux网络 1. UDP 在使用我们的UDP和TCP函数的时候&#xff0c;我们需要理解一些预备的知识&#xff1a; 源 IP 地址和目的 IP 地址&#xff1a; 在网络通信中&#xff0c;IP …

电脑软件:推荐一款非常好用的图片编辑软件——Photo Pos Pro

目录 一、软件简介 二、功能介绍 三、使用说明 四、软件特点 一、软件简介 Photo Pos Pro 4是一款非常实用的图像编辑软件&#xff0c;专为需要修图的用户量身打造而成。软件拥有简洁的用户界面&#xff0c;操作起来也比较简单&#xff0c;能够帮助用户轻松处理图片。软件具…

蚂蚁集团Android一面凉经(2024)

蚂蚁集团Android一面凉经(2024) 笔者作为一名双非二本毕业7年老Android, 最近面试了不少公司, 目前已告一段落, 整理一下各家的面试问题, 打算陆续发布出来, 供有缘人参考。今天给大家带来的是《蚂蚁集团Android一面凉经(2024)》。 面试职位: 蚂蚁集团-Android/iOS开发工程师-支…

MySQL练手 --- 1174. 即时食物配送 II

题目链接&#xff1a;1174. 即时食物配送 II 思路&#xff1a; 题目要求&#xff1a;即时订单在所有用户的首次订单中的比例。保留两位小数 其实也就是 即时订单 / 首次订单 所以&#xff0c;先求出首次订单&#xff0c;在首次订单的基础上寻找即时订单即可 解题过程&#x…

介绍下PolarDB

业务中用的是阿里云自研的PolarDB&#xff0c;分析下PolarDB的架构。 认识PolarDB 介绍 PolarDB是阿里云自研的&#xff0c;兼容MySQL、PostageSQL以及支持MPP的PolarDB-X的高可用、高扩展性的数据库。 架构 部署 云起实验室 - 阿里云开发者社区 - 阿里云 (aliyun.com) 数…

IDEA新建module后变为普通文件夹

问题描述&#xff1a; 在父项目中创建module并构建子父关系&#xff0c;但在创建module并配置后出现未生效问题 在父项目中的pom.xml文件中添加 <modules><module>***</module></modules>在新建Module中添加 <parent><groupId>com.***&l…

安卓打包apk中加密与不加密的区别与具体设置

在安卓应用开发中&#xff0c;对 APK&#xff08;Android Package Kit&#xff09;文件进行加密主要是为了保护应用不受恶意攻击&#xff0c;比如防止应用被反编译、二次打包等。下面我将详细介绍加密与不加密的区别以及具体的设置方法。 加密与不加密的区别 1. 安全性: • 加…

C 语言动态顺序表

test.h #ifndef _TEST_H #define _TEST_H #include <stdio.h> #include <stdlib.h> #include <string.h>typedef int data_type;// 定义顺序表结构体 typedef struct List{data_type *data; // 顺序表数据int size; // 顺序表当前长度int count; // 顺序表容…

嵌入式中什么是三次握手

在开始前刚好我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「嵌入式的资料从专业入门到高级教程」&#xff0c;点个关注在评论区回复“666”之后私信回复“666”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&#xff01; 在网络数据传输中&#xf…

Qt自定义带前后缀图标的PushButton

写在前面 Qt提供QPushButton不满足带前后缀图标的需求&#xff0c;因此考虑自定义实现带前后缀图标的PushButton&#xff0c;方便后续快速使用。 效果如下&#xff1a; 同时可设置前后缀图标和文本之间间隙&#xff1a; 代码实现 通过前文介绍的Qt样式表底层实现 可以得…

C++——将有序数组转换为二叉搜索树leetcode108

C——将有序数组转换为二叉搜索树leetcode108 题目描述思路代码 题目描述 给你一个整数数组 nums &#xff0c;其中元素已经按 升序 排列&#xff0c;请你将其转换为一棵平衡二叉搜索树。 思路 二叉搜索树是任意左子树的节点值小于右子树的节点值&#xff0c;二叉搜索树的中…