C++学习笔记----4、用C++进行程序设计(四)---- 复合关系与继承关系之间的细线

        在现实世界只是很容易区分对象之间是复合关系还是继承关系。没有人会说桔子有一个水果--而只能是桔子是一种水果。但是,在代码中,有时候就不是那么清晰了。

        设想有一个代表关联数组的假想类,将一个键影射到一个值的数据结构。例如,一家保险公司可以使用一个AssociativeArray类将成员ID影射到一个名字,这样的话,给出一个ID,就会很容易地找到对应的成员的名字。成员ID就是键,成员名字就是值。

        在标准关联数组实现中,一个键与一个值相关联。如果ID 12345影射到成员名字为“张三”,它就不能再影射到成员名字“李四”。在大部分实现中,如果你想对一个已经有值的键再加第二个值,原来的值就会消失。换句话说,如果ID 12345影射到了“张三”,你又给ID 12345赋了一个“李四”,那么张三就会马上脱保。这个可以以下顺序展示,对假设的insert()成员函数调用了再次,关联数组的内容结果如下:

myArray.insert(12345, "张三");
12345“张三”【字符串】
myArray.insert(12345, "李四");
12345“李四”【字符串】

        使用像关联数组但是允许给定的键有多个值的数据结构并不难。像在保险的那个例子中,一个家庭可能有多个名字对应到了同一个ID。因为这样的一个数据结构与关联数组比较类似,去稍微修改一下其功能还是不错的。一个关联数组对于一个键只能有一个值,但这个值可以是任何值。除了字符串之外,也可以是一个集合(比如vector),一个键包含了多个值。对于一个已有的ID来讲,每次增加一个成员,就是给集合增加了一个名字。将以以下顺序起作用:

Collection collection;                 // 声名一个集合.
collection.insert("张三");             // 给集合添加一个新的元素.
myArray.insert(12345, collection);     //将集合插入数组.
12345{“张三”}【集合】
Collection collection { myArray.get(12345) }; // 访问已有集合.
collection.insert("李四");                     // 集合中增加一个新元素.
myArray.insert(12345, collection);             // 用更新后的集合进行替换
12345{“李四”}【集合】

        使用集合而不是字符串会比较讨厌,需要大量重复的代码。最好将这个多值功能封装到一个单独的类里面,可能会叫做MultiAssociativeArray,MultiAssociativeArray类与AssociativeArray比较像,除了背后的实现,它会存储集合的每一个字符串值而不是一个单独的字符串。当然了,MultiAssociativeArray在某种程度上与AssociativeArray相关联,因为它依然使用关联数组来保存数据。不清晰的是这种关系到底是复合关系还是继承关系。

        我们先来说继承关系,先认为MultiAssociativeArray是AssociativeArray的继承类。最终我们会认为这是一个坏主意,但让我们做一个坏的设计的例子。MultiAssociativeArray必须覆盖成员函数,拉回数组的接入,这样的话,或者生成集合并添加成员,或者访问已有集合并添加成员。也必须覆盖访问值的成员函数。这就复杂了,虽然:覆盖后的get()成员函数需要返回一个单独的值,而不是一个集合。那MultiAssociativeArray应该返回什么呢?一个选择就是返回与键相关联的第一个值。可以加一个getAll()的成员函数来返回与键相关联的所有值。这比较像一个可信的设计。虽然它覆盖了基础类的所有成员函数,但它仍然使用了基础类的所有成员函数。下图为其UML类图。

           现在再来考虑一下复合关系,MultiAssociativeArray是自身的类,但是它包含了一个AssociativeArray对象。它可能有一个与AssociativeArray相类似的接口,但是不需要一样。背后的实际情况是,当用户给MultiAssociativeArray添加东西时,实际是在集合中添加了一个AssociativeArray对象。这看起来很完美,如下图:

        好了,哪种解决方案是对的?看起来没有正确答案,但大量的实践告诉我们,在这两种方法中复合关系通常更好。主要原因是不需要担心保持关联数组功能的情况下允许修改暴露在外的接口。例如,上面图示的get()成员函数就被修改成了getAll(),在MultiAssociativeArray中很清晰地对于给定的键获得了其所有值。还有,对于复合关系,你不需要担心关联数组功能的渗漏。例如,对于继承关系,如果关联数组支持一个能够获得值的总数的成员函数,它就会报告集合的数字,除非MultiAssociativeArray知道去覆盖它。但大家对这种渗露一般都不会太注意,会造成很大的问题。

         也就是说,有人会提出一种这样的观点,MultiAssociativeArray实际上就是带有一些新的功能的AssociativeArray,那它应该是一种继承关系。这种观点认为在两种关系之间有时候有一条细线,你需要考虑类要怎样用,是否你要建立的就是从其他类中利用一些功能,还是确实是对类进行了修改或者增加了新功能。

        以下列表列出了对MultiAssociativeArray类使用哪种方法的赞成和反对的意见:

继承关系复合关系
赞成原因

从基础上来说,是对于不同特点的相同的抽象。

提供了(绝大多数)与AssociativeArray相同的成员函数

MultiAssociativeArray可以有任何有用的成员函数而不必担心AssociativeArray有的成员函数。

实现上可以进行任何除了AssociativeArray之外的修改,只要不改变对外暴露的成员函数。

反对原因

关联数组的定义就是一个键对着一个值。说MultiAssociativeArray是一个数组就是对数组的亵渎!

MultiAssociativeArray覆盖了AssociativeArray的两个成员函数,这就是设计出了问题的强烈信号。

AssociativeArray的不知道或者不合适的属性或成员函数可能是对MultiAssociativeArray的渗漏。

某种意义上,MultiAssociativeArray通过新的成员函数重新造了轮子。

AssociativeArray的一些增加的属性与成员函数可能会比较有用。

               在这个案例中不使用继承关系的原因是很站得住脚的。还有,利斯科夫取代原理(LSP)可以帮助你在使用继承与复合关系之间做出决定。该原理指出,在不改变行为的前提下是可以使用继承类而不是基类的。应用到这个例子中,它指出这一定是一个复合关系,因为在使用AssociativeArray之前无法直接使用MultiAssociativeArray。如果你硬要这么干的话,行为就会发生改变。例如,AssociativeArray的insert()成员函数对于在数组中已经存在的同样的键改掉了原来的值,而MultiAssociativeArray不会改掉这些值。

        以上两种解决方案实际上不是仅有的可能的解决方案。其他的选择可以是让AssociativeArray包含一个MultiAssociativeArray,或者是两者都继承于一个通用的基类,等等。对于特定的设计你可以想出多种解决方案。

        如果你必须要在这两种关系之间进行选择,我还是推荐,根据多年的经验,使用复合关系吧。

        记住,在这儿展示继承关系与复合关系之间的不同的AssociativeArray与MultiAssociativeArray,在你自己的代码中,推荐使用标准关联数组类而不是要自己去写。C++标准库提供了std::map可以代替AssociativeArray,std::multimap可以代替MultiAssociativeArray。

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

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

相关文章

【Canvas与艺术】环形橄榄枝纹饰

【成图】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>环形橄榄枝(draft2)</title><style type"text/css&quo…

MySql 高阶二(SQL 性能分析)

SQL 性能分析&#xff1a; 查看当前数据库的 增删改查的使用情况 show global status like Com_______;慢查询日志&#xff1a; -- 查看状态 show variables like slow_query_log目前是开启状态。如何开启&#xff0c;编辑my.cnf 文件 添加下面的语句&#xff0c;编辑完成后…

Ansible远程自动化运维

目录 概念 安装ansible modules模块和语法 命令行语法 模块 1. command 基础模块 常用的参数 2. shell模块 3. cron定时任务模块 4. user用户管理模块 参数 5. copy复制模块 参数 6. file模块 设置文件属性 参数 实验&#xff1a;批量创建目录 7…

设备实时数据采集:开启制造业智能化、自动化的新篇章

传统制造业在进行生产过程中&#xff0c;会涉及到设备实时数据采集需求&#xff0c;这些数据对于监控生产流程、优化生产效率、保证产品质量以及降低成本等方面至关重要。以下是一些常见的数据采集需求&#xff1a; 1.生产数据&#xff1a;包括生产数量、生产批次、生产速度等&…

如何禁止编辑PDF文件?推荐两种方法!

在日常工作中&#xff0c;我们经常会遇到需要分享重要的PDF文件的情况&#xff0c;但又希望文件内容不被随意更改。为此&#xff0c;设置PDF文件的修改限制是一个非常有效的措施。今天分享两种常见的禁止修改PDF的方法&#xff0c;一起来看看如何设置。 方法一&#xff1a;使用…

FPGA开发——DS18B20读取温度并且在数码管上显示

一、简介 在上一篇文章中我们对于DS18B20的相关理论进行了详细的解释&#xff0c;同时也对怎样使用DS18B20进行了一个简单的叙述。在这篇文章我们通过工程来实现DS18B20的温度读取并且实现在数码管伤显示。 1、基本实现思路 根据不同时刻的操作&#xff0c;我们可以使用一个状…

TCP与UDP传输的学习

void *memset(void *s, int c, size_t n); 功能&#xff1a;将一块内存空间的每个字节都设置为指定的值&#xff1b;这个函数通常用于初始化一个内存空间&#xff0c;或者清空一个空间&#xff1b; 参数&#xff1a;viod * s 空类型指针&#xff0c;指向要填充内存块&#xf…

代码随想录训练营 Day37打卡 动态规划 part05 完全背包理论基础 518. 零钱兑换II 377. 组合总和 Ⅳ 卡码70. 爬楼梯(进阶版)

代码随想录训练营 Day37打卡 动态规划 part05 一、完全背包理论基础 有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i]&#xff0c;得到的价值是value[i] 。每件物品都有无限个&#xff08;也就是可以放入背包多次&#xff09;&#xff0c;求解将哪些物品装…

Arco Design,字节跳动出品的UI库

Arco Design是字节跳动出品的UI库&#xff0c;支持Vue和React。还是比较美观的。并且Arco Design还提供了中后台模版。但是通过提供的arco-cli连接了github&#xff0c;正常情况下无法构建。但效果还是挺好的&#xff0c;下面是效果图&#xff1a; 更新&#xff1a; 传送门可…

【C++ Primer Plus习题】2.3

问题: 解答: #include <iostream> using namespace std;void print01() {cout << "三只眼瞎的老鼠" << endl; }void print02() {cout << "看他们怎么跑" << endl; }int main() {print01();print01();print02();print02();r…

Linux:Linux多线程

目录 线程概念 什么是线程 二级页表 线程的优点 线程的缺点 线程异常 线程用途 Linux进程VS线程 进程和线程 进程的多个线程共享 进程和线程的关系 Linux线程控制 POSIX线程库 线程创建 线程等待 线程终止 分离线程 线程ID及进程地址空间布局 线程概念 什么…

VS Code 远程连接SSH服务

随着技术的不断迭代更新&#xff0c;在 Linux 系统中使用 Vim、nano 等基于 Shell 终端的编辑器&#xff08;我曾经也是个 vimer&#xff0c;但是 VS Code 实在太香了&#xff09;&#xff0c;已经很难适应当下的开发效率。因此大多数开发者开始使用 VS Code 远程连接 Linux 系…

博物馆地图导览:利用GIS与蓝牙定位技术,融合语音解说功能

引言 亲爱的技术员、开发者朋友们&#xff0c;随着科技的不断进步&#xff0c;博物馆等文化场所的导览方式也在不断创新。今天&#xff0c;我将为大家介绍我们的新产品——博物馆地图导览系统&#xff0c;该系统集成了GIS&#xff08;地理信息系统&#xff09;、蓝牙定位技术以…

xss靶场详解

目录 1.第一题 2.第二题 3.第三题 4.第四题 5.第五题 6.第六题 7.第七题 8.第八题 1.第一题 在源码script标签里边&#xff0c;innerhtml是用于访问或修改 HTML 元素内的 HTML 内容的&#xff0c;这里是访问spaghet这个元素的&#xff0c;并通过括号里面的东西搜索当前…

【图机器学习系列】(二)从传统机器学习角度理解图(一)

微信公众号&#xff1a;leetcode_algos_life&#xff0c;代码随想随记 小红书&#xff1a;412408155 CSDN&#xff1a;https://blog.csdn.net/woai8339?typeblog &#xff0c;代码随想随记 GitHub: https://github.com/riverind 抖音【暂未开始&#xff0c;计划开始】&#xf…

面试题详解

前言&#xff1a;这一期我们专门来巩固所学知识&#xff0c;同时见识一些面试题。对知识做出一个总结。 1 不创建临时变量交换两个整数 . 第一种方法 #include<stdio.h> int main() {int a 0;int b 0;scanf("%d %d", &a, &b);printf("交换前…

深度学习 --- VGG16卷积核的可视化(JupyterNotebook实战)

VGG16卷积核的可视化 在前一篇文章中&#xff0c;我对VGG16输入了一张图像&#xff0c;并实现了VGG16各层feature map的可视化。深度学习 --- VGG16各层feature map可视化(JupyterNotebook实战)-CSDN博客文章浏览阅读615次&#xff0c;点赞13次&#xff0c;收藏15次。在VGG16模…

Linux三剑客-sedawk

一、三剑客-sed命令 1、格式 sed 找谁干啥 文件 找谁:条件&#xff0c;匹配哪一行&#xff0c;哪些行. 干啥:动作&#xff0c;增删改查. #显示文件的第3行 sed -n 3p /etc/passwd选项说明-n取消默认输出-p查找-rsed支持扩展正则-i修改文件内容&#xff0c;这个选项放在最后…

【C++ Primer Plus习题】3.5

问题: 解答: #include <iostream> using namespace std;int main() {long long populationWorld 0;long long populationChina 0;cout << "请输入全球的人口数:";cin >> populationWorld;cout << "请输入中国的人口数:";cin &g…

XSS- - - DOM 破坏案例与靶场

目录 链接靶场&#xff1a; 第一关 Ma Spaghet 第二关 Jefff 第三关 Ugandan Knuckles 第四关 Ricardo Milos 第五关 Ah Thats Hawt 第六关 Ligma 第七关 Mafia 第八关 Ok, Boomer 链接靶场&#xff1a; XS…