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

news2024/11/17 16:48:58

文章目录

  • 一、面向对象五大原则
    • 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); // 假设支付金额为100
    
    return 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.coloradmin.cn/o/2242293.html

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

相关文章

基于图像分类的对抗攻击算法研究

图像分类与对抗攻击 图像分类是计算机视觉的基础任务&#xff0c;旨在 将不同类别的图像准确归类 。随着深度学习发展&#xff0c;模型在大规模数据集上的表现已超越人类。然而&#xff0c;这一进步也引发了新的安全挑战—— 对抗攻击 。 对抗攻击通过向原始图像添加精心设计的…

【AI大模型】ELMo模型介绍:深度理解语言模型的嵌入艺术

学习目标 了解什么是ELMo.掌握ELMo的架构.掌握ELMo的预训练任务.了解ELMo的效果和成绩.了解ELMo的优缺点. 目录 &#x1f354; ELMo简介 &#x1f354; ELMo的架构 2.1 总体架构 2.2 Embedding模块 2.3 两部分的双层LSTM模块 2.4 词向量表征模块 &#x1f354; ELMo的预…

Linux Android 正点原子RK3568替换开机Logo完整教程

0.这CSDN是有BUG吗?大家注意:表示路径的2个点号全都变成3个点号啦! 接下来的后文中,应该是2个点都被CSDN变成了3个点: 1.将这两个 bmp 图片文件720x1280_8bit拷贝到内核源码目录下,替换内核源码中默认的 logo 图片。注意:此时还缺少电量显示图片 2.编译内核 make d…

【EasyExcel】复杂导出操作-自定义颜色样式等(版本3.1.x)

文章目录 前言一、自定义拦截器二、自定义操作1.自定义颜色2.合并单元格 三、复杂操作示例1.实体(使用了注解式样式)&#xff1a;2.自定义拦截器3.代码4.最终效果 前言 本文简单介绍阿里的EasyExcel的复杂导出操作&#xff0c;包括自定义样式&#xff0c;根据数据合并单元格等。…

Linux(CentOS)安装达梦数据库 dm8

CentOS版本&#xff1a;CentOS 7 达梦数据库版本&#xff1a;dm8 一、获取 dm8 安装文件 1、下载安装文件 打开达梦官网&#xff1a;https://www.dameng.com/ 下载的文件 解压后的文件 2、上传安装文件到 CentOS 使用FinalShell远程登录工具&#xff0c;并且使用 root 用户…

fastapi 调用ollama之下的sqlcoder模式进行对话操作数据库

from fastapi import FastAPI, HTTPException, Request from pydantic import BaseModel import ollama import mysql.connector from mysql.connector.cursor import MySQLCursor import jsonapp FastAPI()# 数据库连接配置 DB_CONFIG {"database": "web&quo…

如何监控Kafka消费者的性能指标?

要监控 Kafka 消费者性能指标&#xff0c;可以遵循以下最佳实践和策略&#xff1a; 关键性能指标监控&#xff1a; 消息吞吐量&#xff1a;监控消费者和生产者的吞吐量&#xff0c;以评估数据处理和消费的效率。延迟&#xff1a;监控端到端的延迟&#xff0c;例如通过比较消息产…

【LINUX相关】

一、Linux怎么进行查看日志&#xff1f; 首先得问问开发项目日志存放在哪里&#xff0c;可以使用多种命令来查看日志。常用的命令包括tail、cat、less和grep等。例如:1、使用tail命令可以实时查看日志文件的最新内容&#xff1a;tail -f log_file&#xff0c; 2、使用cat命令可…

IT运维的365天--019 用php做一个简单的文件上传工具

前情提要&#xff1a;朋友的工作室&#xff0c;有几个网站分布在不同的服务器上&#xff0c;要经常进行更新&#xff0c;之前是手动复制压缩包到各个服务器去更新&#xff08;有写了自动更新的Shell脚本&#xff09;。但还是觉得太麻烦&#xff0c;每次还要手动传输压缩包到各个…

计算机网络 (4)计算机网络体系结构

前言 计算机网络体系结构是指计算机网络层次结构模型&#xff0c;它是各层的协议以及层次之间的端口的集合。这一体系结构为计算机网络及其部件应完成的功能提供了精确定义&#xff0c;并规定了这些功能应由何种硬件或软件来实现。 一、主流模型 计算机网络体系结构存在多种模型…

C++- 基于多设计模式下的同步异步日志系统

第一个项目:13万字,带源代码和详细步骤 目录 第一个项目:13万字,带源代码和详细步骤 1. 项目介绍 2. 核心技术 3. 日志系统介绍 3.1 为什么需要⽇志系统 3.2 ⽇志系统技术实现 3.2.1 同步写⽇志 3.2.2 异步写⽇志 4.知识点和单词补充 4.1单词补充 4.2知识点补充…

小程序租赁系统打造便捷租赁体验助力共享经济发展

内容概要 小程序租赁系统是一个极具创新性的解决方案&#xff0c;它通过简化租赁过程&#xff0c;让物品的共享变得便捷流畅。对于那些有闲置物品的用户来说&#xff0c;他们可以轻松发布自己的物品&#xff0c;让其他需要的人快速找到并租借。而对于找东西的人来说&#xff0…

EXCEL 或 WPS 列下划线转驼峰

使用场景&#xff1a; 需要将下划线转驼峰&#xff0c;直接在excel或wps中第一行使用公式&#xff0c;然后快速刷整个列格式即可。全列工下划线转为格式&#xff0c;使用效果如下&#xff1a; 操作步骤&#xff1a; 第一步&#xff1a;在需要显示驼峰的一列&#xff0c;复制以…

【SpringBoot】公共字段自动填充

问题引入 JavaEE开发的时候&#xff0c;新增字段&#xff0c;修改字段大都会涉及到创建时间(createTime)&#xff0c;更改时间(updateTime)&#xff0c;创建人(craeteUser)&#xff0c;更改人(updateUser)&#xff0c;如果每次都要自己去setter()&#xff0c;会比较麻烦&#…

华为云租户网络-用的是隧道技术

1.验证租户网络是vxlan 2.验证用OVS 2.1控制节点VXLAN 本端ip&#xff08;local ip&#xff09;192.168.31.8 2.2计算节点VXLAN 本端ip&#xff08;local ip&#xff09;192.168.31.11 计算节点用的是bond0做隧道网络 2.3查看bond文件是否主备模式

网络编程-002-UDP通信

1.UDP通信的简单介绍 1.1不需要通信握手,无需维持连接,网络带宽需求较小,而实时性要求高 1.2 包大小有限制,不发大于路径MTU的数据包 1.3容易丢包 1.4 可以实现一对多,多对多 2.客户端与服务端=发送端与接收端 代码框架 收数据方一般都是客户端/接收端 3.头文件 #i…

从PE结构到LoadLibrary

从PE结构到LoadLibrary PE是Windows平台主流可执行文件格式,.exe , .dll, .sys, .com文件都是PE格式 32位的PE文件称为PE32&#xff0c;64位的称为PE32&#xff0c;PE文件格式在winnt.h头中有着详细的定义&#xff0c;PE文件头包含了一个程序在运行时需要的所有信息&#xff…

AntFlow:一款高效灵活的开源工作流引擎

AntFlow 是一款功能强大、设计优雅的开源工作流引擎&#xff0c;其灵感来源于钉钉的工作流设计理念&#xff0c;旨在为企业和开发者提供灵活、高效的工作流解决方案。AntFlow 支持复杂的业务流程管理&#xff0c;具有高度可定制性&#xff0c;且拥有现代化的前端设计&#xff0…

智慧安防丨以科技之力,筑起防范人贩的铜墙铁壁

近日&#xff0c;贵州省贵阳市中级人民法院对余华英拐卖儿童案做出了一审宣判&#xff0c;判处其死刑&#xff0c;剥夺政治权利终身&#xff0c;并处没收个人全部财产。这一判决不仅彰显了法律的威严&#xff0c;也再次唤起了社会对拐卖儿童犯罪的深切关注。 余华英自1993年至2…

python机器人Agent编程——多Agent框架的底层逻辑(上)

目录 一、前言二、两个核心概念2.1 Routines&#xff08;1&#xff09;清晰的Prompt&#xff08;2&#xff09;工具调用json schema自动生成&#xff08;3&#xff09;解析模型的toolcall指令&#xff08;4&#xff09;单Agent的循环决策与输出 PS.扩展阅读ps1.六自由度机器人相…