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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

引领豪华MPV新趋势,比亚迪夏内饰科技广州车展全球首发

11月15日&#xff0c;比亚迪第五代DM技术中大型旗舰MPV夏内饰科技在广州车展正式发布。作为王朝网全新IP夏的首款同名车型&#xff0c;夏采用王朝新一代内饰设计语言&#xff0c;传承华夏文化深厚底蕴&#xff0c;从技术、平台、安全、设计、空间、智享、智驾七大维度&#xff…

flutter字体大小切换案例 小字体,标准字体,大字体,超大字体案例

flutter字体大小切换案例 小字体&#xff0c;标准字体&#xff0c;大字体&#xff0c;超大字体案例 Android iOS设备带有选择记录 我的flutter项目版本 environment: sdk: ‘>3.4.4 <4.0.0’ 图片案例 pubspec.yaml 添加依赖 # 屏幕尺寸适配 https://github.com/OpenF…

Schnorr 和 BLS 算法详解

Schnorr 签名和 BLS 签名在区块链技术中都有着重要的应用。它们各自具备独特的优势&#xff0c;使其在不同的区块链应用场景中得到广泛使用。 Schnorr签名算法 Schnorr签名算法是一种基于离散对数问题的数字签名算法&#xff0c; 由德国密码学家 克劳斯施诺尔 &#xff08;Cl…

OpenHarmony的公共事件

OpenHarmony的公共事件 公共事件简介 CES&#xff08;Common Event Service&#xff0c;公共事件服务&#xff09;为应用程序提供订阅、发布、退订公共事件的能力。 公共事件分类 公共事件从系统角度可分为&#xff1a;系统公共事件和自定义公共事件。 系统公共事件&#…

vue3中ElementPlus引入下载icon图标不显示透明问题解决教程方法

问题&#xff1a;今天用vue3开发&#xff0c;使用ElementPlus图标引入了但是不显示&#xff0c;是空白透明 解决&#xff1a; 1、在main.js中引入element-plus/icons-vue图标库 import * as ElIcons from element-plus/icons-vue; // 引入图标库 2、注册所有图标 // 注册所有…

性能测试中的核心指标

在性能测试中&#xff0c;核心指标是用来评估系统性能和表现的关键指标。这些指标可以帮助测试人员了解系统在不同负载和场景下的表现&#xff0c;以便发现系统的瓶颈和问题。以下是性能测试中的几个核心指标。 1、响应时间 响应时间是指系统在收到请求后&#xff0c;从接收请…

星际流浪的大模型

种子世界还在太空漫游&#xff0c;航线上捡到一个铁盒子&#xff0c;那是一块硬盘&#xff0c;古老的东西。 长老就安排歌者&#xff0c;你去研究&#xff0c;查查硬盘的来源坐标。 费好大劲&#xff0c;歌者把硬盘中的程序和数据激活&#xff0c;运行了起来。 很有意思&#x…

Docker部署Kafka SASL_SSL认证,并集成到Spring Boot

1&#xff0c;创建证书和密钥 需要openssl环境&#xff0c;如果是Window下&#xff0c;下载openssl Win32/Win64 OpenSSL Installer for Windows - Shining Light Productions 还需要keytool环境&#xff0c;此环境是在jdk环境下 本案例所使用的账号密码均为&#xff1a; ka…

每日论文20-24RFIC四核三模带自动模式跟踪输出缓冲器的VCO

前段时间赶tapeout要死要活&#xff0c;有一段时间没看&#xff0c;现在继续。 《An 18.5-to-36.5 GHz 206.8 dBc/Hz FoMT Quad-Core Triple-Mode VCO with Automatic-Mode-Tracking Output Buffers 》24RFIC 今年的RFIC&#xff0c;四核三模&#xff0c;桥位于每个线圈的中心…

Vue3 虚拟列表组件库 virtual-list-vue3 的使用

Vue3 虚拟列表组件库 virtual-list-vue3 的基本使用 分享个人写的一个基于 Vue3 的虚拟列表组件库&#xff0c;欢迎各位来进行使用与给予一些更好的建议&#x1f60a; 概述&#xff1a;该组件组件库用于提供虚拟化列表能力的组件&#xff0c;用于解决展示大量数据渲染时首屏渲…

rust逆向初探

rust 逆向葵花宝典 rust逆向技巧 rust逆向三板斧&#xff1a; [!NOTE] 快速定位关键函数 (真正的main函数)&#xff1a;观察输出、输入&#xff0c;字符串搜索&#xff0c;断点等方法。定位关键 加密区 &#xff1a;根据输入的flag&#xff0c;打硬件断点&#xff0c;快速捕获…

SPIRiT-Diffusion:基于自一致性驱动的加速MRI扩散模型|文献速递-基于深度学习的病灶分割与数据超分辨率

Title 题目 SPIRiT-Diffusion: Self-Consistency Driven Diffusion Model for Accelerated MRI SPIRiT-Diffusion&#xff1a;基于自一致性驱动的加速MRI扩散模型 01 文献速递介绍 磁共振成像&#xff08;MRI&#xff09; 在临床和研究领域被广泛应用。然而&#xff0c;其…

微信小程序_小程序视图与逻辑_day3

一、目标 A. 能够知道如何实现页面之间的导航跳转 B. 能够知道如何实现下拉刷新效果 C. 能够知道如何实现上拉加载更多效果 D. 能够知道小程序中常用的生命周期 二、目录 A. 页面导航 B. 页面事件 C. 生命周期 D. WXS脚本 E. 案例-本地生活&#xff08;列表页面&#xff09;…

数字化转型企业架构设计手册(交付版),企业数字化转型建设思路、本质、数字化架构、数字化规划蓝图(PPT原件获取)

1、企业架构现状分析 2、企业架构内容框架 3、企业架构设计方法 3.1 、业务架构设计方法 3.2 、数据架构设计方法 3.3 、应用架构设计方法 3.4 、技术架构设计方法 软件全套资料部分文档清单&#xff1a; 工作安排任务书&#xff0c;可行性分析报告&#xff0c;立项申请审批表&…

maven的optional选项说明以及具体应用

写在前面 本文看下maven的optional选项的作用和用法。 1&#xff1a;什么作用 考虑这样的场景&#xff0c;A依赖B&#xff0c;B依赖C&#xff0c;正常的按照依赖的传递性&#xff0c;A也会间接的依赖C&#xff0c;但是在一些特定的场景中项目A只希望依赖B&#xff0c;而不依…

FPGA使用Verilog实现CAN通信

FPGA实现CAN通信&#xff08;Verilog&#xff09; 1.作者使用的方法是通过FPGA芯片&#xff08;如Xilinx公司的型号为XC7K325TFFG676-2&#xff09;控制SJA1000T芯片&#xff08;CAN控制器芯片&#xff09;实现CAN通信&#xff0c;如下图所示&#xff1a; 2.熟悉连接方式之后&…

1Panel 推送 SSL 证书到阿里云、腾讯云

本文首发于 Anyeの小站&#xff0c;点击链接 访问原文体验更佳 前言 都用 CDN 了还在乎那点 1 年证书钱么&#xff1f; 开句玩笑话&#xff0c;按照 Apple 的说法&#xff0c;证书有效期不该超过 45 天。那么证书有效期的缩短意味着要更频繁地更新证书。对于我这样的“裸奔”…

推荐一款高效的网站数据抓取工具:SysNucleus WebHarvy

SysNucleus WebHarvy是一款高效的网站数据抓取工具&#xff0c;支持从网页中提取文本、图像、URL 和电子邮件等内容&#xff0c;无需编写任何代码或脚本即可轻松实现数据抓取。用户可以通过 WebHarvy 内置的浏览器直观地浏览网页&#xff0c;指引软件提取所需的数据。它通过自动…

Codeforces Round 987 (Div. 2)(前四道)

A. Penchick and Modern Monument 翻译&#xff1a; 在繁华大都市马尼拉的摩天大楼中&#xff0c;菲律宾最新的 Noiph 购物中心刚刚竣工&#xff01;建筑管理方 Penchick 订购了一座由 n 根支柱组成的先进纪念碑。 纪念碑支柱的高度可以用一个由 n 个正整数组成的数组 h 来表示…