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依赖的数据库对象,完美的解决了前面的示例存在的问题,极大的提升了程序的可扩展性。

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

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

相关文章

计算机网络中的域名系统(DNS)及其优化技术

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 计算机网络中的域名系统&#xff08;DNS&#xff09;及其优化技术 计算机网络中的域名系统&#xff08;DNS&#xff09;及其优化…

STM32单片机CAN总线汽车线路通断检测

目录 目录 前言 一、本设计主要实现哪些很“开门”功能&#xff1f; 二、电路设计原理图 1.电路图采用Altium Designer进行设计&#xff1a; 2.实物展示图片 三、程序源代码设计 四、获取资料内容 前言 随着汽车电子技术的不断发展&#xff0c;车辆通信接口在汽车电子控…

第二十一课 Vue组件实用示例

Vue组件实用示例 本课主要介绍组件的一些小练习&#xff0c;通过这些小练习巩固下之前课程中的学习 1&#xff09;组件中值的双向绑定 <div id"app"><test></test> </div> <script>Vue.component(test, {template: <div><…

(实战)WebApi第13讲:怎么把不同表里的东西,包括同一个表里面不同的列设置成不同的实体,所有的给整合到一起?【前端+后端】、前端中点击标签后在界面中显示

一、实现全局跨域&#xff1a;新建一个Controller&#xff0c;其它的controller都继承它 1、新建BaseController 2、在后端配置&#xff0c;此处省略【详情见第12讲四、3、】 3、其它的控制器继承BaseController&#xff0c;这个时候就能够完成全局的跨域 【向后台传cookie和…

前缀和技巧解析

前缀和技巧解析 前缀和&#xff08;Prefix Sum&#xff09;是一种常用的算法技巧&#xff0c;用于高效地处理一系列连续子数组和的问题。通过构建一个额外的数组来存储从数组起始位置到当前位置的累计和&#xff0c;可以在常数时间内快速计算任意区间的和。 前缀和应用的典型…

云安全之云计算基础

0x00 前言 本文主要是针对云计算相关的基础梳理和整理。 云计算 NIST 800-145ISO/IEC 17788ISO/IEC 17789云安全 NIST 500-299 云安全ISO / IEC FDIS 27017 云安全0x01 云计算基础 什么是云计算: 一种新的运作模式和一组用于管理计算资源共享池的技术。云计算是一种颠覆性的…

Mysql每日一题(行程与用户,困难※)

今天给大家分享一个截止到目前位置&#xff0c;我遇到最难的一道mysql题目&#xff0c;非常建议大家亲手做一遍 完整代码如下&#xff0c;这道题的主要难点是它有两个外键&#xff0c;以前没遇到过&#xff0c;我也没当回事&#xff0c;分享一下错误经验哈 当时我写的where判断…

离线 快速搭建 docker docker-compose k8s 环境

所需资源 sealos_5.0.1_linux_arm64.tar.gzkubernetes.tar等docker-compose-linux-aarch64 离线安装sealos&#xff0c;用于安装k8sdocker 首先安装sealos工具 tar zxvf sealos_5.0.1_linux_arm64.tar.gz sealos && chmod x sealos && mv sealos /usr/bin*…

Python的Web请求:requests库入门与应用

Python的Web请求&#xff1a;requests库入门与应用 在Python中&#xff0c;进行网络请求和获取数据是许多应用程序的基础功能。requests库是Python中最流行的HTTP库之一&#xff0c;它以简洁、易用、功能强大的特点著称&#xff0c;可以帮助开发者高效地进行各种类型的Web请求…

已解决:spark代码中sqlContext.createDataframe空指针异常

这段代码是使用local模式运行spark代码。但是在获取了spark.sqlContext之后&#xff0c;用sqlContext将rdd算子转换为Dataframe的时候报错空指针异常 Exception in thread "main" org.apache.spark.sql.AnalysisException: java.lang.RuntimeException: java.lang.Nu…

【Electron】Electron Forge如何支持Element plus?

在 Electron Forge 项目中集成 Element Plus 是一个相对直接的过程。Element Plus 是一个基于 Vue 3 的 UI 组件库&#xff0c;因此你需要确保你的 Electron 项目已经集成了 Vue 3。以下是集成 Element Plus 到 Electron Forge 项目的步骤&#xff1a; 1. 初始化 Electron For…

WebGIS四大地图框架:Leaflet、OpenLayers、Mapbox、Cesium

地理信息系统&#xff08;GIS&#xff09;已经成为现代应用开发中不可或缺的一部分&#xff0c;尤其在前端开发中。随着Web技术的快速发展&#xff0c;许多强大而灵活的GIS框架涌现出来&#xff0c;为开发人员提供了丰富的工具和功能&#xff0c;使他们能够创建交互式、高性能的…

cooladmin 后端 查询记录

查询记录&#xff1a;pageQueryOp中列表查询的group by node ts controller代码如下 import { CoolController, BaseController } from cool-midway/core; import { Inject, Post, Get, Param } from midwayjs/decorator; import { ComparePricesPlanInfoEntity } from ../../…

UE5运行时创建slate窗口

加入"Slate","SlateCore"模块 Actor.cpp // Fill out your copyright notice in the Description page of Project Settings.#include "MyWindowClass.h"// Sets default values AMyWindowClass::AMyWindowClass() {// Set this actor to call…

react 受控组件和非受控组件

在 React 中&#xff0c;受控组件和非受控组件是两种处理表单元素&#xff08;如输入框、选择框等&#xff09;值的方式。 1. 受控组件 受控组件是指 React 组件的表单元素的值是由 React 组件的 state 来管理的。换句话说&#xff0c;React 会全程控制表单元素的值&#xff…

cesium 3DTiles之pnts格式详解

Point Cloud 1 概述 点云&#xff08;Point Cloud&#xff09;瓦片格式用于高效流式传输大规模点云数据&#xff0c;常用于 3D 可视化中。每个点由位置&#xff08;Position&#xff09;和可选的属性定义&#xff0c;这些属性用来描述点的外观&#xff08;如颜色、法线等&…

基于STM32的智能家居安防系统设计

引言 本项目基于STM32微控制器设计了一个智能家居安防系统&#xff0c;通过集成多个传感器模块和远程报警系统&#xff0c;实现对家庭的安全监控和自动化报警功能。该系统能够实时监测家中的门窗状态、烟雾浓度、以及是否有非法入侵等状况&#xff0c;并在检测到异常时通过蜂鸣…

用枚举算法解决LeetCode第3348题最小可整除数位乘积II

3348.最小可整除数位乘积II 难度&#xff1a;困难 问题描述&#xff1a; 给你一个字符串num&#xff0c;表示一个正整数&#xff0c;同时给你一个整数t。 如果一个整数没有任何数位是0&#xff0c;那么我们称这个整数是无零数字。 请你返回一个字符串&#xff0c;这个字符…

F5全新报告揭示AI时代API安全面临严峻挑战

F5 《2024年应用策略现状报告:API安全》揭示了 API 保护中的漏洞以及对全面安全措施的迫切需求 西雅图,2024年11月11日 – F5(NASDAQ: FFIV)日前发布《2024年应用策略现状报告:API 安全》(以下简称为“报告”),揭示了跨行业API安全面临的严峻现状。该报告强调了企业API保护方面…

【SpringBoot】20 同步调用、异步调用、异步回调

Git仓库 https://gitee.com/Lin_DH/system 介绍 同步调用&#xff1a;指程序在执行时&#xff0c;调用方需要等待函数调用返回结果后&#xff0c;才能继续执行下一步操作&#xff0c;是一种阻塞式调用。 异步调用&#xff1a;指程序在执行时&#xff0c;调用方在调用函数后立…