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

C++ 编程基础(5)类与对象 | 5.8、面向对象五大原则

文章目录

  • 一、面向对象五大原则
    • 1、单一功能(Single Responsibility Principle, SRP)
    • 2、开放封闭原则(Open/Closed Principle, OCP)
    • 3、里氏替换原则(Liskov Substitution Principle, LSP)
    • 4、接口隔离原则(Interface Segregation Principle, ISP)
    • 5、依赖倒置原则(Dependency Inversion Principle, DIP)

前言:

在软件开发领域,面向对象编程(OOP)是一种重要的编程范式,它通过封装、继承和多态等特性,提高了代码的可重用性、灵活性和可维护性。C++作为一种强大的面向对象编程语言,充分体现了这些原则。在面向对象的设计中,有五大核心原则被广泛认可和应用,它们分别是:单一职责原则(SRP)、开放封闭原则(OCP)、里氏替换原则(LSP)、接口隔离原则(ISP)和依赖倒置原则(DIP)。下面,将逐一解析这五大原则在C++中的应用。

一、面向对象五大原则

1、单一功能(Single Responsibility Principle, SRP)

一个类应该只有一个引起变化的原因,即一个类只负责一项职责。这个原则强调类的专注性,避免一个类承担过多的责任。当一个类承担多个职责时,其内聚力会降低,代码的可读性和可维护性也会受到影响。

2、开放封闭原则(Open/Closed Principle, OCP)

软件实体(类、模块、函数等)应该对扩展开放,对修改封闭。这个原则鼓励通过继承和多态来实现功能的扩展,而不是通过修改现有代码。这样可以在不改变原有代码的基础上添加新功能,提高系统的灵活性和可维护性。

示例背景:

假设有一个支付系统,最初只支持信用卡支付。随着业务的发展,需要添加对其他支付方式的支持,如 PayPal 和比特币。为了遵循开放封闭原则,可以设计一个抽象的支付接口,然后为每种支付方式创建具体的实现类。这样,当需要添加新的支付方式时,只需要创建一个新的实现类并将其添加到系统中即可,无需修改现有的代码。

#include <iostream>
#include <string>
#include <vector>// 抽象的支付接口
class IPayment {
public:virtual void pay(double amount) = 0;virtual ~IPayment() {}
};// 信用卡支付的具体实现
class CreditCardPayment : public IPayment {
public:void pay(double amount) override {std::cout << "Paying " << amount << " using Credit Card." << std::endl;}
};// PayPal支付的具体实现
class PayPalPayment : public IPayment {
public:void pay(double amount) override {std::cout << "Paying " << amount << " using PayPal." << std::endl;}
};// 比特币支付的具体实现
class BitcoinPayment : public IPayment {
public:void pay(double amount) override {std::cout << "Paying " << amount << " using Bitcoin." << std::endl;}
};// 支付处理类
class PaymentProcessor {
private:std::vector<IPayment*> payments;
public:void addPaymentMethod(IPayment* payment) {payments.push_back(payment);}void processPayments(double amount) {for (IPayment* payment : payments) {payment->pay(amount);}}~PaymentProcessor() {for (IPayment* payment : payments) {delete payment;}}
};int main() {// 创建支付处理器PaymentProcessor processor;// 添加不同的支付方式processor.addPaymentMethod(new CreditCardPayment());processor.addPaymentMethod(new PayPalPayment());processor.addPaymentMethod(new BitcoinPayment());// 处理支付processor.processPayments(100.0); // 假设支付金额为100return 0;
}

3、里氏替换原则(Liskov Substitution Principle, LSP)

子类型必须能够替换掉它们的基类型。这个原则强调继承关系中的一致性。如果一个派生类不能替代其基类而不改变程序的正确性,那么这个继承关系就是不合理的。

示例背景:

下面给出一个违反里氏替换原则的示例,假设有一个基类 Bird 和一个派生类 Penguin,如下:

#include <iostream>
using namespace std;class Bird {
public:virtual void fly() {cout << "I can fly!" << endl;}
};class Penguin : public Bird {
public:void fly() override {cout << "I cannot fly!" << endl;}
};

在这个例子中,Bird 类有一个 fly 方法,该方法输出 I can fly!Penguin 类继承自 Bird 并重写了 fly 方法,输出 I cannot fly!

在这个例子中,Penguin 类违背了里氏替换原则,因为它改变了基类 Birdfly 方法的行为。根据里氏替换原则,子类对象应该能够替换父类对象而不改变程序的正确行为。为了避免这种情况,应该确保子类在重写父类的方法时,不会改变其原有的行为契约。

4、接口隔离原则(Interface Segregation Principle, ISP)

不应该强迫客户依赖于它们不使用的方法。这个原则强调接口的粒度。一个接口应该只包含客户需要的方法,避免接口过于庞大和复杂。

示例背景:

示例中 IShape 接口包含了三个方法:drawgetAreagetPerimeter。但是,如果有一个只关心形状面积的客户,它不需要实现 drawgetPerimeter 方法。为了遵循 ISP,可以将接口拆分为更小的接口。

class IShape {
public:virtual void draw() const = 0;virtual int getArea() const = 0;virtual int getPerimeter() const = 0;
};class Circle : public IShape {
public:void draw() const override { /* ... */ }int getArea() const override { /* ... */ }int getPerimeter() const override { /* ... */ }
};

5、依赖倒置原则(Dependency Inversion Principle, DIP)

高层模块不应该依赖于低层模块,二者都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象。这个原则强调通过抽象来解耦模块之间的依赖关系。高层模块应该依赖于抽象接口,而不是具体的实现类。这样可以提高系统的灵活性和可扩展性。

示例背景:

假设现在要做一个电商系统,需要实现的基本功能是订单入库。

版本一:违反依赖倒置原则

假设系统设计初期,用的是SQL Server数据库。通常会定义一个SqlServer类,用于数据库的读写。然后定义一个Order类,负责订单的逻辑处理。由于订单要入库,需要依赖于数据库的操作。因此在Order类中,需要定义SqlServer类的变量并初始化。

// 定义SqlServer类负责与数据库进行交互
class SqlServer {
public:void add() {cout<<"往数据库添加一个订单."<<endl;}
};// 定义Order类处理业务,并使用SqlServer类提供的能力,实现订单入库的功能
class Order {
private:SqlServer *p;
public:Order() {p = new SqlServer;}void add() {// 先进行订单的逻辑处理,再把这个订单放到数据库p->add();}
};

如果要使用Oracle数据库,那么要重新写一个OracleServer类,然后对Order类进行修改,程序扩展性比较差。上面程序扩展性不强的原因主要有下面两个

  • Order直接依赖于一个具体的类。
  • Order依赖的对象的创建与绑定是在它的内部实现的。

下面的示例重点分析了下如何解决这两个问题

版本二:符合依赖倒置原则

为了解决Order直接依赖于一个具体的类的问题,可以定义一个抽象类DataAccess,类DataAccess提供了操作数据库的接口,Order类依赖抽象类DataAccess,如下:

class DataAccess {
public:virtual void add() {}
} ;class SqlServer : public DataAccess {
public:void add() {cout<<"往 SQL 数据库添加一个订单."<<endl;}
};class Oracle : public DataAccess {
public:void add() {cout<<"往 Oracle 数据库添加一个订单."<<endl;}
};class Order {
private:DataAccess &re;
public:Order(DataAccess &re):re(re) {}void add() {// 先进行订单的逻辑处理,再把这个订单放到数据库re.add();}
};

通过控制反转(Inversion of Control,缩写为IoC)可以解决前面的第二个问题,下面先介绍下什么是控制反转,以及如何实现控制反转。

控制反转:

  • 定义: 控制反转是一种设计思想,它将对象的控制权从代码本身转移到外部容器或框架中。具体来说,在采用控制反转之前,对象通常会自己负责创建并管理它所依赖的其他对象。而在控制反转中,对象的依赖关系会在其创建时或运行时由外部实体(如IoC容器)注入。
  • 实现方式: 控制反转最常见的实现方式是依赖注入(Dependency Injection,简称DI)。依赖注入允许在运行时动态地将依赖关系注入到对象中,从而降低了对象之间的耦合度。依赖注入有多种实现形式,包括:
    • 构造器注入: 通过构造器将依赖对象传递给被依赖的对象。
    • Setter方法注入: 通过Setter方法将依赖对象设置到被依赖的对象中。
    • 接口注入: 通过接口将依赖对象注入到被依赖的对象中。

可以通过构造函数,将Order依赖的数据库对象注入给它,如下:

class Order{
private:DataAccess &re;
public:// 通过构造函数接受依赖的数据库对象Order(DataAccess &re):re(re) {}void add() {// 先进行订单的逻辑处理,再把这个订单放到数据库re.add();}
};int main() {SqlServer sql;         // 在外部创建依赖对象Order order1(sql);     // 通过构造函数注入依赖order1.add();Oracle oracle;         // 在外部创建依赖对象Order order2(oracle);  // 通过构造函数注入依赖order2.add();return 0;
}

Order依赖抽象类DataAccess以及通过构造函数来注入Order依赖的数据库对象,完美的解决了前面的示例存在的问题,极大的提升了程序的可扩展性。

相关文章:

C++ 编程基础(5)类与对象 | 5.8、面向对象五大原则

文章目录 一、面向对象五大原则1、单一功能&#xff08;Single Responsibility Principle, SRP&#xff09;2、开放封闭原则&#xff08;Open/Closed Principle, OCP&#xff09;3、里氏替换原则&#xff08;Liskov Substitution Principle, LSP&#xff09;4、接口隔离原则&am…...

node.js中express的基本了解

定义 Express是基于Node.js平台&#xff0c;快速、开放、极简的Web开发框架。 本质 Express是一个npm上的第三方包&#xff0c;提供了快速创建Web服务器的便捷方法。 作用 与Node.js内置的http模块类似&#xff0c;Express也是专门用来创建Web服务器的&#xff0c;但它极大地简…...

AI大模型(一):Prompt AI编程

一、Prompt Engineering&#xff0c;提示工程 提示工程也叫指令工程&#xff1a; Prompt是发给大模型的指令&#xff0c;比如【讲个睡前故事】、【用Python写个消消乐游戏】等&#xff1b;本质上大模型相关的工程工作&#xff0c;都是围绕prompt展开的&#xff1b;提示工程门…...

ArcGIS Pro属性表乱码与字段名3个汉字解决方案大总结

01 背景 我们之前在使用ArcGIS出现导出Excel中文乱码及shp添加字段3个字被截断的情况&#xff0c;我们有以下应对策略&#xff1a; 推荐阅读&#xff1a;ArcGIS导出Excel中文乱码及shp添加字段3个字被截断&#xff1f; 那如果我们使用ArGIS Pro出现上述问题&#xff0c;该如何…...

小程序-基于java+SpringBoot+Vue的驾校预约平台设计与实现

项目运行 1.运行环境&#xff1a;最好是java jdk 1.8&#xff0c;我们在这个平台上运行的。其他版本理论上也可以。 2.IDE环境&#xff1a;IDEA&#xff0c;Eclipse,Myeclipse都可以。推荐IDEA; 3.tomcat环境&#xff1a;Tomcat 7.x,8.x,9.x版本均可 4.硬件环境&#xff1a…...

计算机网络网关简介

网关&#xff0c;在计算机网络中扮演着至关重要的角色&#xff0c;它如同不同语言间的翻译官&#xff0c;让不同网络协议、不同体系结构的网络能够相互通信。简而言之&#xff0c;网关就是一个网络连接到另一个网络的“关口”&#xff0c;负责数据的接收、转换与发送。 在局域…...

如何用python将pdf转换为json格式

使用 Python 将 PDF 文件转换为 JSON 格式&#xff0c;主要步骤如下&#xff1a; 读取 PDF 内容&#xff1a;首先使用一个库读取 PDF 文件内容&#xff0c;如 PyMuPDF 或 pdfplumber。这些库可以逐页提取文本&#xff0c;并返回结构化的数据。 组织数据到 JSON&#xff1a;将提…...

STL关联式容器介绍

在前文中介绍了STL的序列式容器&#xff1b; STL序列式容器之vector-CSDN博客 STL序列式容器之list-CSDN博客 STL序列式容器之deque-CSDN博客 STL序列式容器之stack-CSDN博客 STL序列式容器之queue-CSDN博客 STL序列式容器之heap&#xff08;堆&#xff09;-CSDN博客 ST…...

java计算机毕业设计选题参考3000篇

基于微信小程序的springboot高校餐厅食品留样管理系统 springboot vue大学生创新创业训练项目管理系统 Springboot的疫情网课管理系统 基于微信小程序的计算机实验室排课与查询系统ssm后端 基于ssm后端的学生购电电费管理微信小程序weixin356 ssm机场网上订票系统 基于ssmvue的…...

JWT介绍、测试案例 以及实际开发中的使用

什么是JWT&#xff1f; JWT&#xff0c;通过数字签名的方式&#xff0c;以json对象为载体&#xff0c;在不同的服务终端之间安全的传输信息&#xff0c;用来解决传统session的弊端。 JWT在前后端分离系统&#xff0c;通过JSON形式作为WEB应用中的令牌(token)&#xff0c;用于…...

快排和归并

目录 前言 快速排序 相遇位置一定比key小的原理&#xff08;大&#xff09;&#xff1a; 避免效率降低方法&#xff08;快排优化&#xff09; 三数取中&#xff08;选key优化&#xff09; 小区间优化 hoare版本快排 挖坑法快排 前后指针快排 非递归快排 归并排序 非递…...

VUE+SPRINGBOOT实现邮箱注册、重置密码、登录功能

随着互联网的发展&#xff0c;网站用户的管理、触达、消息通知成为一个网站设计是否合理的重要标志。目前主流互联网公司都支持手机验证码注册、登录。但是手机短信作为服务端网站是需要付出运营商通信成本的&#xff0c;而邮箱的注册、登录、重置密码&#xff0c;无疑成为了这…...

Vue 项目打包后环境变量丢失问题(清除缓存),区分.env和.env.*文件

Vue 项目打包后环境变量丢失问题&#xff08;清除缓存&#xff09;&#xff0c;区分.env和.env.*文件 问题背景 今天在导报项目的时候遇到一个问题问题&#xff1a;在开发环境中一切正常&#xff0c;但在打包后的生产环境中&#xff0c;某些环境变量&#xff08;如 VUE_APP_B…...

创建vue+electron项目流程

一个vue3和electron最基本的环境搭建步骤如下&#xff1a;// 安装 vite vue3 vite-plugin-vue-setup-extend less normalize.css mitt pinia vue-router npm create vuelatest npm i vite-plugin-vue-setup-extend -D npm i less -D npm i normalize.css -S &#xff0…...

3. 用Ruby on Rails创建一个在线商城

哎呀&#xff0c;你这是想要我写一篇超长篇的Ruby on Rails教程啊&#xff01;好吧&#xff0c;既然你这么热情&#xff0c;那我就勉为其难地给你来一篇生动有趣、充满比喻夸张讽刺修辞手法的教程吧&#xff01; 1. 准备工作 1.1. 安装Ruby和Rails 1.1.1 安装Ruby 下载Ruby…...

jmeter常用配置元件介绍总结之配置元件

系列文章目录 1.windows、linux安装jmeter及设置中文显示 2.jmeter常用配置元件介绍总结之安装插件 3.jmeter常用配置元件介绍总结之线程组 4.jmeter常用配置元件介绍总结之函数助手 5.jmeter常用配置元件介绍总结之取样器 6.jmeter常用配置元件介绍总结之jsr223执行pytho…...

SpringBoot获取请求参数

spring boot获取请求参数 文章目录 spring boot获取请求参数一、简单参数二、实体参数三、数组集合参数四、日期参数五、Json参数六、路径参数 开头概述 在Spring Boot框架中&#xff0c;处理HTTP请求并获取请求参数是开发Web应用程序中的一项基本任务。无论是简单的GET请求还是…...

【数据结构】树——顺序存储二叉树

写在前面 在学习数据结构前&#xff0c;我们早就听说大名鼎鼎的树&#xff0c;例如什么什么手撕红黑树大佬呀&#xff0c;那这篇笔记不才就深入浅出的介绍二叉树。 文章目录 写在前面一、树的概念及结构1.1、数的相关概念1.2、数的表示1.3 树在实际中的运用&#xff08;表示文…...

Android中perform和handle方法的区别——以handleLaunchActivity与performLaunchActivity为例

在Android系统中&#xff0c;perform和handle方法经常出现在关键流程中&#xff0c;分别承担不同的职责。这种命名约定反映了框架设计中的分层思想&#xff0c;帮助开发者区分任务的调度与实现。本文通过handleLaunchActivity和performLaunchActivity这两个典型方法的源码分析&…...

聊聊依赖性测试

在软件测试中&#xff0c;我们常常面临一个挑战&#xff1a;多个模块之间高度耦合&#xff0c;任何一个模块的异常都可能导致整个系统崩溃。如何确保这些模块之间的协作无缝衔接&#xff1f;这就需要依赖性测试的助力&#xff01; 什么是依赖性测试&#xff1f;它与功能测试、…...

手游刚开服就被攻击怎么办?如何防御DDoS?

开服初期是手游最脆弱的阶段&#xff0c;极易成为DDoS攻击的目标。一旦遭遇攻击&#xff0c;可能导致服务器瘫痪、玩家流失&#xff0c;甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案&#xff0c;帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...

调用支付宝接口响应40004 SYSTEM_ERROR问题排查

在对接支付宝API的时候&#xff0c;遇到了一些问题&#xff0c;记录一下排查过程。 Body:{"datadigital_fincloud_generalsaas_face_certify_initialize_response":{"msg":"Business Failed","code":"40004","sub_msg…...

day52 ResNet18 CBAM

在深度学习的旅程中&#xff0c;我们不断探索如何提升模型的性能。今天&#xff0c;我将分享我在 ResNet18 模型中插入 CBAM&#xff08;Convolutional Block Attention Module&#xff09;模块&#xff0c;并采用分阶段微调策略的实践过程。通过这个过程&#xff0c;我不仅提升…...

【Java学习笔记】Arrays类

Arrays 类 1. 导入包&#xff1a;import java.util.Arrays 2. 常用方法一览表 方法描述Arrays.toString()返回数组的字符串形式Arrays.sort()排序&#xff08;自然排序和定制排序&#xff09;Arrays.binarySearch()通过二分搜索法进行查找&#xff08;前提&#xff1a;数组是…...

Opencv中的addweighted函数

一.addweighted函数作用 addweighted&#xff08;&#xff09;是OpenCV库中用于图像处理的函数&#xff0c;主要功能是将两个输入图像&#xff08;尺寸和类型相同&#xff09;按照指定的权重进行加权叠加&#xff08;图像融合&#xff09;&#xff0c;并添加一个标量值&#x…...

C++ 求圆面积的程序(Program to find area of a circle)

给定半径r&#xff0c;求圆的面积。圆的面积应精确到小数点后5位。 例子&#xff1a; 输入&#xff1a;r 5 输出&#xff1a;78.53982 解释&#xff1a;由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982&#xff0c;因为我们只保留小数点后 5 位数字。 输…...

LeetCode - 199. 二叉树的右视图

题目 199. 二叉树的右视图 - 力扣&#xff08;LeetCode&#xff09; 思路 右视图是指从树的右侧看&#xff0c;对于每一层&#xff0c;只能看到该层最右边的节点。实现思路是&#xff1a; 使用深度优先搜索(DFS)按照"根-右-左"的顺序遍历树记录每个节点的深度对于…...

HDFS分布式存储 zookeeper

hadoop介绍 狭义上hadoop是指apache的一款开源软件 用java语言实现开源框架&#xff0c;允许使用简单的变成模型跨计算机对大型集群进行分布式处理&#xff08;1.海量的数据存储 2.海量数据的计算&#xff09;Hadoop核心组件 hdfs&#xff08;分布式文件存储系统&#xff09;&a…...

短视频矩阵系统文案创作功能开发实践,定制化开发

在短视频行业迅猛发展的当下&#xff0c;企业和个人创作者为了扩大影响力、提升传播效果&#xff0c;纷纷采用短视频矩阵运营策略&#xff0c;同时管理多个平台、多个账号的内容发布。然而&#xff0c;频繁的文案创作需求让运营者疲于应对&#xff0c;如何高效产出高质量文案成…...

FFmpeg:Windows系统小白安装及其使用

一、安装 1.访问官网 Download FFmpeg 2.点击版本目录 3.选择版本点击安装 注意这里选择的是【release buids】&#xff0c;注意左上角标题 例如我安装在目录 F:\FFmpeg 4.解压 5.添加环境变量 把你解压后的bin目录&#xff08;即exe所在文件夹&#xff09;加入系统变量…...