检查面向对象的程序的结构
面向对象的编程(OOP)使用对象对真实实体进行建模。
对于熟悉结构化编程的开发人员,结构化编程与面向对象的编程之间的比较有助于阐明这两种方法之间的差异。 此外,提高对封装和类生命周期的理解有助于开发安全可靠的解决方案。
将 Object-Oriented 编程与结构化编程进行比较
结构化编程和面向对象的编程(OOP)是软件开发的两种不同的方法,每个方法都有自己的原则和方法集。 结构化编程基于一种自上而下的方法,其中程序分为更小、可管理的功能或过程。 此方法强调使用循环、条件和子例程的明确和逻辑控制流。 面向对象的编程围绕封装数据和行为的对象组织软件设计,从而提升更模块化和可重用的代码结构。 虽然结构化编程侧重于要执行的作序列,但面向对象的编程强调作所涉及的对象。
我们可以使用建筑项目的实际示例作为隐喻来说明结构化编程与面向对象的编程之间的差异。
Object-Oriented 编程示例
假设面向对象的编程是设计和构建城市。 在此隐喻中,每个建筑代表一个类,每个建筑的房间和设施都代表该类的属性和方法。 正如城市由各种建筑组成,每个建筑都提供特定用途(住宅、商业、工业),OOP 计划由各种类组成,每个课程旨在处理特定任务。 建筑物(类)是基于蓝图(类定义)构造的,每个建筑物可以有多个实例(对象),这些实例(对象)共享同一结构,但可以有不同的状态(数据)。
在这个城市,封装就像保护其内部结构的建筑物的墙壁,只允许通过指定的门和窗户(方法)进行访问。 封装可确保建筑物(类)的内部工作从外部世界隐藏,并与建筑物的交互是受控和安全。
这个城市建筑的隐喻突出了 OOP 的模块化和可重用性,其中每个类(建筑)都可以独立开发、测试和维护,但他们都共同努力,形成一个凝聚力和功能城市(计划)。 正如规划良好的城市允许高效管理和可伸缩性一样,设计良好的 OOP 计划可促进可维护性、灵活性和可伸缩性。
结构化编程示例
想象一下结构化编程,使一个被子。 在此隐喻中,每个构造部分都表示程序中的函数或过程。 就像被子由许多单独的补丁组成,一个结构化程序由各种功能组成,旨在执行特定任务。 每个函数都类似于可以独立开发、测试和维护的修补程序。 按特定顺序将这些补丁缝在一起时,可以创建一个完整的被子,就像合并函数以形成完整的程序一样。
在结构化编程中,重点是控制逻辑流,就像分位器如何规划布局和修补程序序列来创建一个凝聚力的设计一样。 仲裁程序可确保每个补丁与其他人完美配合,保持清晰有序的模式。 同样,结构化程序员可确保每个函数无缝地适应整个程序,并通过使用循环、条件和子例程来保持清晰和逻辑的控制流。
这种被子隐喻突出了结构化编程的模块化特性,每个函数(或修补程序)可以根据需要重复使用和重新排列。 正如仲裁程序可以替换或修改单个修补程序而不中断整个被子一样,程序员可以更新或优化单个函数,而不会影响整个程序。 这种模块化使结构化编程成为创建清晰、可维护且可重用的代码的有效方法。 但是,随着程序的大小和复杂性的增长,管理函数之间的交互可能会变得更加具有挑战性。 正如包含过多修补程序的被子可能会变得笨拙且难以管理一样,具有大量功能和过程的结构化程序可能会变得繁琐且难以维护。 随着修补程序(函数)的数量的增加,跟踪它们如何组合和交互变得更加具有挑战性。 这会导致代码重复、调试困难和缺乏凝聚力等问题。 在大型应用程序中,结构化编程的线性和自上而下的方法可能导致相互依赖的函数的纠缠 Web,使得难以理解和修改代码库。 这种复杂性可能会阻碍可伸缩性和可维护性,最终影响软件的整体质量和性能。
检查 Object-Oriented 编程中的类的使用
类是面向对象的编程(OOP)的构建基块,用于定义程序中对象的结构和行为。 了解封装和类生命周期提供的优势有助于了解面向对象的编程的工作原理。
封装
封装是面向对象的编程(OOP)的基本原则之一。 它是指将数据(字段)和方法(行为)捆绑在一个单元(通常是类)中。 封装会限制对某些对象组件的直接访问,这可以防止意外修改数据。
封装具有以下优势:
数据隐藏:封装允许对象的内部状态从外部隐藏。 这意味着可以更改对象的内部表示形式,而不会影响使用该对象的外部代码。 例如,通过使用专用字段并提供公共 getter 和 setter 方法,可以控制数据的访问和修改方式。
改进了可维护性:封装使维护和修改代码更容易。 对类的内部实现的更改不会影响使用该类的代码,前提是公共接口保持不变。 这种关注点分离使开发人员能够专注于代码的特定部分,而无需担心意外的副作用。
提高灵活性:封装允许更灵活和模块化的代码。 通过定义明确的接口,可以轻松替换或更新代码的各个部分,而不会影响其他部分。 这种模块化使重复使用代码并从更简单的组件生成复杂系统变得更加容易。
增强的安全性:封装通过防止未经授权的访问和修改来帮助保护对象数据的完整性。 通过方法控制对数据的访问,可以强制实施对数据的使用方式的规则和约束。 此强制措施有助于防止 bug,并确保对象保持有效状态。
抽象:封装通过仅向外部世界公开对象的必要详细信息,支持抽象的概念。 这简化了接口,并使它更易于理解和使用对象。 对象的用户不需要知道内部实现,这可降低代码复杂性并提高可读性。
注意
封装是隐藏类用户不需要的数据成员。 使用 private 访问器关键字封装或隐藏数据成员。 使用属性和方法控制对隐藏字段变量的访问。 无法直接访问隐藏的数据成员。
public class Person
{
// Private fields
private string firstName;
private string lastName;
private int age;
// Public properties with getters and setters
public string FirstName
{
get { return firstName; }
set { firstName = value; }
}
public string LastName
{
get { return lastName; }
set { lastName = value; }
}
public int Age
{
get { return age; }
set
{
if (value >= 0)
{
age = value;
}
else
{
throw new ArgumentException("Age can't be negative");
}
}
}
// Public method
public void Introduce()
{
Console.WriteLine($"Hi, I'm {FirstName} {LastName}, and I'm {Age} years old.");
}
}
在此示例中:
- 字段
firstName、lastName和ageprivate,这意味着不能直接从类外部访问它们。 - 公共属性
FirstName、LastName和Age提供对专用字段的控制访问权限。 -
Age属性包括验证逻辑,以确保不能将年龄设置为负值。 -
Introduce方法提供了一种在不公开内部实现详细信息的情况下与对象数据交互的方法。
类生命周期
在 C# 应用程序中,类的生命周期涉及从其定义到最终销毁的几个阶段。 类的生命周期包括以下步骤:
- 类定义:使用其成员定义类。
- 编译:将类编译为 IL 代码。
- 加载:将程序集加载到内存中。
- 实例化:创建类的实例。
- 初始化:初始化对象的字段和属性。
- 用法:使用应用程序中的对象。
- 垃圾回收:不再需要对象时回收对象的内存。
- 销毁:执行清理逻辑并释放内存。
下面是一个示例,其中包含类生命周期中每个步骤的说明:
类定义
定义:类在其属性、方法和其他成员的源代码中定义。
例如:
public class Person { // auto-implemented properties for name and age public string FirstName { get; set; } public string LastName { get; set; } public int Age { get; set; } // method to introduce the person public void Introduce() { Console.WriteLine($"Hi, I'm {FirstName} {LastName}, and I'm {Age} years old."); } }汇编
编译:源代码由 C# 编译器编译为中间语言(IL)。 IL 代码存储在程序集(DLL 或 EXE 文件中)。
例如:
Person类编译为 IL 代码,并包含在程序集中。装载
加载:当应用程序运行时运行时(CLR)将程序集加载到内存中。
例如:包含
Person类的程序集由 CLR 加载到内存中。实例
实例化:使用
new关键字创建类的实例。 调用类的构造函数来初始化对象。例如:
Person person1 = new Person { FirstName = "Tim", LastName = "Shao", Age = 25 };初始化
初始化:构造函数初始化对象的字段和属性。 执行构造函数中定义的任何初始化逻辑。
示例:使用
Person、person1和FirstName的指定值初始化LastName对象Age。用法
用法:对象在应用程序中使用。 根据需要访问和修改对象的方法和属性。
例如:
person1.Introduce();垃圾回收
垃圾回收:当不再需要对象且没有引用对象时,垃圾回收器(GC)将回收对象使用的内存。 如果定义了析构函数(终结器),将调用它。
示例:如果代码中的任何位置不再引用
person1,GC 最终将回收其内存。破坏
销毁:释放对象的内存,并执行析构函数(如果指定了终结器)中定义的任何清理逻辑。
示例:
Person对象person1被销毁,其内存由 GC 回收。