面向对象与设计模式第一节:深入理解OOP

第三章:面向对象与设计模式

第一节:深入理解OOP

面向对象编程(OOP)是一种编程范式,它将程序结构视为由对象组成,促进了代码的重用性和可维护性。在这一课中,我们将深入分析OOP的四个基本特性:封装、继承、多态和抽象,并提供相应的示例与实践。

1. OOP基本特性
1.1 封装

封装是OOP的核心概念之一,它指的是将对象的状态(属性)和行为(方法)组合在一起,同时隐藏内部实现细节,只暴露必要的接口。这使得对象可以保护自己的数据不被外部干扰,从而提高了代码的安全性和稳定性。

示例

class BankAccount {
private:double balance; // 账户余额public:BankAccount(double initial_balance) : balance(initial_balance) {}void deposit(double amount) {if (amount > 0) {balance += amount;}}void withdraw(double amount) {if (amount > 0 && amount <= balance) {balance -= amount;}}double getBalance() const {return balance;}
};

在上面的示例中,balance属性被声明为私有(private),外部代码无法直接访问。只有通过公共方法(depositwithdrawgetBalance),才能修改或获取余额。

1.2 继承

继承允许一个类从另一个类派生,从而获得其属性和行为。这种机制支持代码重用和逻辑结构的组织,使得程序设计更加灵活。

示例

class SavingsAccount : public BankAccount {
private:double interestRate; // 利率public:SavingsAccount(double initial_balance, double rate): BankAccount(initial_balance), interestRate(rate) {}void applyInterest() {deposit(getBalance() * interestRate);}
};

在这个例子中,SavingsAccount类继承自BankAccount类,得到了其所有的属性和方法,并增加了一个新的方法applyInterest,用于计算利息。

1.3 多态

多态允许对象以不同的形式表现。这意味着可以使用同一接口来处理不同类型的对象。这通常通过虚函数实现,使得程序可以在运行时选择调用哪个函数。

示例

class Shape {
public:virtual void draw() const = 0; // 纯虚函数
};class Circle : public Shape {
public:void draw() const override {// 画圆的逻辑}
};class Rectangle : public Shape {
public:void draw() const override {// 画矩形的逻辑}
};void render(const Shape& shape) {shape.draw(); // 动态调用
}

在这个示例中,Shape是一个基类,定义了一个纯虚函数drawCircleRectangle类实现了这个函数。通过传递Shape引用,render函数能够根据实际对象类型动态调用相应的draw方法。

1.4 抽象

抽象是指通过提取对象的共同特征来创建类。抽象类通常包含纯虚函数,无法实例化。它们提供了一个模板,其他类可以从中继承并实现特定的功能。

示例

class Animal {
public:virtual void makeSound() const = 0; // 抽象方法
};class Dog : public Animal {
public:void makeSound() const override {// 狗叫的逻辑}
};class Cat : public Animal {
public:void makeSound() const override {// 猫叫的逻辑}
};

在这个例子中,Animal是一个抽象类,定义了一个纯虚函数makeSoundDogCat类实现了这个函数,表示它们各自的叫声。

2. 类的设计原则与实际案例

设计原则帮助开发者编写可维护、可扩展和可重用的代码。以下是几个常用的设计原则:

2.1 单一职责原则(SRP)

一个类应该只有一个单一的职责,所有的功能都应该围绕这个职责展开。这可以减少类的复杂性,便于维护和修改。

示例

class User {
public:void login() {// 登录逻辑}
};class UserNotifier {
public:void notifyUser() {// 通知用户逻辑}
};

在这个示例中,User类负责用户登录,而UserNotifier类负责用户通知。两个类各司其职,符合单一职责原则。

2.2 开放封闭原则(OCP)

软件实体(类、模块、函数等)应该对扩展开放,对修改封闭。这意味着可以通过添加新代码而不是修改现有代码来实现新功能。

示例

class Shape {
public:virtual double area() const = 0; // 纯虚函数
};class Circle : public Shape {
public:double area() const override {// 计算圆的面积}
};class Rectangle : public Shape {
public:double area() const override {// 计算矩形的面积}
};// 新增多边形类
class Polygon : public Shape {
public:double area() const override {// 计算多边形的面积}
};

在这个例子中,新增的Polygon类并没有修改现有的代码,而是通过继承Shape类实现新的功能,符合开放封闭原则。

2.3 里氏替换原则(LSP)

子类对象应该能够替换父类对象,而不会影响程序的正确性。这意味着子类必须符合父类的约定。

示例

void drawShape(const Shape& shape) {shape.draw(); // 可以接受任何形状的子类
}

确保CircleRectangle等子类都能正确执行父类的方法,不会引入错误。

2.4 接口隔离原则(ISP)

不应强迫客户依赖于他们不使用的接口。接口应该细化为特定的、功能单一的接口。

示例

class IShape {
public:virtual void draw() const = 0;
};class IColor {
public:virtual void fill() const = 0;
};class Circle : public IShape, public IColor {
public:void draw() const override {// 画圆的逻辑}void fill() const override {// 填充圆的逻辑}
};

在这个例子中,IShapeIColor接口分别定义了不同的功能,避免了客户不必要的依赖。

2.5 依赖倒置原则(DIP)

高层模块不应该依赖低层模块,二者都应该依赖抽象。抽象不应该依赖细节,细节应该依赖抽象。

示例

class Notification {
public:virtual void send() = 0; // 抽象通知
};class EmailNotification : public Notification {
public:void send() override {// 发送电子邮件逻辑}
};class SmsNotification : public Notification {
public:void send() override {// 发送短信逻辑}
};class User {Notification* notifier;public:User(Notification* n) : notifier(n) {}void notify() {notifier->send(); // 使用抽象发送通知}
};

在这个示例中,User类依赖于Notification接口而不是具体的通知实现,从而实现了依赖倒置原则。

3. 项目实践

在实际项目中,运用OOP的特性和设计原则是非常重要的。下面是一个基于OOP的简单项目示例。

3.1 项目背景

我们要创建一个简单的图形编辑器,能够支持绘制多种形状并计算其面积。通过OOP的特性,我们可以设计出灵活且易于扩展的结构。

3.2 类的设计
  • Shape类作为抽象基类,定义绘制和计算面积的方法。
  • 具体的形状类(如CircleRectangle)继承自Shape并实现相关方法。
  • ShapeManager类负责管理所有形状,提供添加、删除和遍历功能。
3.3 代码示例
#include <iostream>
#include <vector>
#include <memory>class Shape {
public:virtual void draw() const = 0;virtual double area() const = 0;virtual ~Shape() = default; // 虚析构函数
};class Circle : public Shape {
private:double radius;public:Circle(double r) : radius(r) {}void draw() const override {std::cout << "Drawing Circle with radius: " << radius << std::endl;}double area() const override {return 3.14 * radius * radius;}
};class Rectangle : public Shape {
private:double width, height;public:Rectangle(double w, double h) : width(w), height(h) {}void draw() const override {std::cout << "Drawing Rectangle with width: " << width << " and height: " << height << std::endl;}double area() const override {return width * height;}
};class ShapeManager {
private:std::vector<std::shared_ptr<Shape>> shapes;public:void addShape(std::shared_ptr<Shape> shape) {shapes.push_back(shape);}void drawAll() const {for (const auto& shape : shapes) {shape->draw();}}void calculateTotalArea() const {double totalArea = 0;for (const auto& shape : shapes) {totalArea += shape->area();}std::cout << "Total Area: " << totalArea << std::endl;}
};int main() {ShapeManager manager;manager.addShape(std::make_shared<Circle>(5.0));manager.addShape(std::make_shared<Rectangle>(4.0, 6.0));manager.drawAll();manager.calculateTotalArea();return 0;
}

在这个简单的图形编辑器项目中,我们通过OOP的特性和设计原则,实现了一个可扩展的框架。将来可以很方便地添加新的形状类,而无需修改现有代码。

总结

本课深入探讨了面向对象编程的基本特性,包括封装、继承、多态和抽象,以及如何应用这些特性设计出符合OOP原则的类。在实际开发中,遵循设计原则有助于编写可维护、可扩展的代码。通过简单的项目实例,我们展示了OOP在实际中的应用,帮助初级到中级程序员更好地理解和应用OOP的理念。

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

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

相关文章

Flutter鸿蒙next 布局架构原理详解

✅近期推荐&#xff1a;求职神器 https://bbs.csdn.net/topics/619384540 &#x1f525;欢迎大家订阅系列专栏&#xff1a;flutter_鸿蒙next &#x1f4ac;淼学派语录&#xff1a;只有不断的否认自己和肯定自己&#xff0c;才能走出弯曲不平的泥泞路&#xff0c;因为平坦的大路…

【mysql】转义字符反斜杠,正则表达式

1.mysql插入 1.1插入-反斜杠规则&#xff1a; ​ 在MySQL中&#xff0c;反斜杠在字符串中是属于转义字符&#xff0c;经过语法解析器解析时会进行一次转义&#xff0c;所以当我们insert反斜杠&#xff08;\&#xff09;字符时&#xff0c;如 insert “\” 在数据库中最…

[JAVAEE] 多线程的案例(三) - 线程池

目录 一. 什么是线程池 二. 线程池的作用 三. java提供的线程池类 四. ThreadPoolExecutor的构造方法及参数理解 1. int corePoolSize: 核心线程数. 2. int maximumPoolSize: 最大线程数 核心线程数 非核心线程数 3. int keepAliveTime:非核心线程允许空闲的最大时间. …

DataX简介及使用

目录 一、DataX离线同步工具DataX3.0介绍 1.1、 DataX 3.0概览 1.2、特征 1.3、DataX3.0框架设计 1.4、支持的数据元 1.5、DataX3.0核心架构 1.6、DataX 3.0六大核心优势 1.6.1、可靠的数据质量监控 1.6.2、丰富的数据转换功能 1.6.3、精准的速度控制 1.6.4、强劲的…

第五十四章 安全元素的详细信息 - DerivedKeyToken 详情

文章目录 第五十四章 安全元素的详细信息 - <DerivedKeyToken> 详情详情消息中的位置 第五十四章 安全元素的详细信息 - 详情 <DerivedKeyToken> 的目的是携带发送者和接收者可以独立使用的信息来生成相同的对称密钥。这些方可以使用该对称密钥对 SOAP 消息的相关…

正则表达式和通配符

文章目录 正则表达式和通配符的区别正则表达式&#xff08;Regex&#xff09;通配符&#xff08;Wildcards&#xff09;总结 正则表达式的概念正则表达式的由来为什么要使用正则表达式 正则表达式的语法组成修饰符元字符\f\b\B 在Linux中的基础正则和扩展正则基础正则(BRE)^$.*…

人脸应用实例:性别年龄预测

在当今科技飞速发展的时代&#xff0c;人脸识别技术已经从科幻电影走进了我们的日常生活。通过算法来识别人脸的特征&#xff0c;进而判断身份、年龄和性别&#xff0c;这一技术正逐步改变着我们的生活方式。今天&#xff0c;我们就来探讨一下基于深度学习的人脸应用实例——性…

面试时被问到“Scaling Law”,该怎么答?

在大模型的研发中&#xff0c;通常会有下面一些需求&#xff1a; 计划训练一个 10B 的模型&#xff0c;想知道至少需要多大的数据&#xff1f; 收集到了 1T 的数据&#xff0c;想知道能训练一个多大的模型&#xff1f; 老板准备 1 个月后开发布会&#xff0c;给的资源是 100 …

vue父子传参的方式——Prop

Prop 每一个组件都有一个props的属性&#xff0c;用来接收外部传递的数据 这里我拿一个分页组件为例&#xff1a; 一、基础语法 1、父组件传递数据 父组件在向子组件传递数据时&#xff0c;基础语法如下&#xff1a; <template><div><common-page :pagina…

Linux安装Nginx教程(rpm安装方式)

本章教程,主要介绍如何在Linux Centos7系统上,使用rpm的方式进行安装Nginx。 一、安装wget插件 如果不存在wget下载插件,需要安装一下。 yum install -y wget二 、下载rpm安装包 官方提供的rpm下载地址:https://nginx.org/packages/centos/7/x86_64/RPMS/ <

【Nginx系列】499错误

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

Java面试题——计网篇2

1.get和post请求的区别 用途不同&#xff1a; GET请求用于从服务器获取数据&#xff0c;它不会改变服务器上的数据。POST请求用于向服务器提交数据&#xff0c;通常用于修改服务器上的数据。 数据传输方式不同&#xff1a; GET请求将数据附加在URL后面&#xff0c;通过问号(?)…

Postman常见问题及解决方(全)

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 1、网络连接问题 如果Postman无法发送请求或接收响应&#xff0c;可以尝试以下操作&#xff1a; 检查网络连接是否正常&#xff0c;包括检查网络设置、代理设置…

软考中级嵌入式系统设计师笔记分享(二)

1.TTL 电路是电流控制器件&#xff0c;而CMOS 电路是电压控制器件。 2.TTL 电路的速度快&#xff0c;传输延迟时间短(5-10ns)&#xff0c;但是功耗大。 常见的串行总线有 SPI、II2C、USB、RS232/RS422/RS485、CAN等;高速串行总线主要有 SATA、PCIE、IEEE 1394、Rapidl0、USB 3…

1.DBeaver连接hive数据库

1.hive开启远程服务&#xff0c;linux中直接输入&#xff1a;hiveserver2 2.解压dbeaver和hive-jdbc-2.1.1.zip 3.双击打开 4.数据库&#xff0c;新建连接 5.搜索hive 6.配置参数 7.编辑驱动设置 8.添加jar包 9.测试连接 10.右击&#xff0c;新建sql编辑器 11.执行sql 12.调整字…

在linux中arm-linux-gcc和/usr/bin/gcc有啥区别

在Linux中&#xff0c;arm-linux-gcc和/usr/bin/gcc都是编译器&#xff0c;但它们之间存在显著的区别&#xff0c;主要体现在编译目标、使用场景以及编译生成的二进制文件的可执行性上。而软链接则是Linux文件系统中的一种特殊文件类型&#xff0c;用于创建一个文件的别名。 a…

高级java每日一道面试题-2024年10月24日-JVM篇-说一下JVM有哪些垃圾回收器?

如果有遗漏,评论区告诉我进行补充 面试官: 说一下JVM有哪些垃圾回收器? 我回答: 1. Serial收集器 特点&#xff1a;Serial收集器是最古老、最稳定的收集器&#xff0c;它使用单个线程进行垃圾收集工作。在进行垃圾回收时&#xff0c;它会暂停所有用户线程&#xff0c;即St…

【每日一题】LeetCode - 整数转罗马数字

在罗马数字系统中&#xff0c;七个不同的符号代表不同的值&#xff1a; 符号值I1V5X10L50C100D500M1000 罗马数字的表示方式是从最大值开始逐次减去每个符号的值&#xff0c;通过组合这些符号构建最终的表示形式。本文将介绍一个基于贪心策略的解决方案&#xff0c;将整数转换…

unity开发之Line Renderer

Line Renderer 是一个有用的工具&#xff0c;可让您在游戏中绘制线条。 它可以用作游戏的函数或调试标记。 在这里&#xff0c;让我们创建一个程序&#xff0c;根据基本用法在 Line Renderer 上移动。 目录 如何使用 Line Renderer 和基础知识 在场景中放置 Line Renderer关键组…

音视频同步版本【基于音频】

其实和基于外部时钟的原理操作基本上一模一样。只不过音频帧不需要去匹配现实时钟了&#xff0c;只有视频帧需要匹配现实时钟。而视频帧需要去匹配音频帧的时间&#xff0c;那么就需要给时钟设置一个补偿&#xff0c;因为现在是以音频帧为标准。假如现在现实时钟到了50pts&…