【愚公系列】软考中级-软件设计师 048-面向对象技术(面向对象相关概念)
🏆 作者简介,愚公搬代码
🏆《头衔》:华为云特约编辑,华为云云享专家,华为开发者专家,华为产品云测专家,CSDN博客专家,CSDN商业化专家,阿里云专家博主,阿里云签约作者,腾讯云优秀博主,腾讯云内容共创官,掘金优秀博主,51CTO博客专家等。
🏆《近期荣誉》:2022年度博客之星TOP2,2023年度博客之星TOP2,2022年华为云十佳博主,2023年华为云十佳博主等。
🏆《博客内容》:.NET、Java、Python、Go、Node、前端、IOS、Android、鸿蒙、Linux、物联网、网络安全、大数据、人工智能、U3D游戏、小程序等相关领域知识。
🏆🎉欢迎 👍点赞✍评论⭐收藏
🚀前言
面向对象的编程范式在上世纪六十年代末和七十年代初逐渐形成,并在八十年代得到了广泛应用。它的背景可以追溯到软件开发领域的一些问题和挑战。
在早期,软件开发主要采用的是过程式编程,这种编程方式将程序分解为一系列的过程或函数来完成特定的任务。然而,随着软件系统的不断增大和复杂化,过程式编程面临着一些挑战。
面向对象的编程范式的出现正是为了解决这些问题。它将程序中的数据和行为封装为对象,通过对象之间的交互来完成任务。
缺点 | 解决方法 |
---|---|
缺乏模块化和可重用性:过程式编程往往将功能代码组织为一系列过程和函数,但随着系统的复杂性增加,这些过程和函数之间的关系变得难以管理。代码的重复编写也导致了效率低下和维护困难。 | 面向对象编程通过将功能封装在类中,使得代码更模块化,每个类负责特定的功能。通过类之间的关联和交互,实现代码的可重用性。同时,继承和多态的机制还进一步提供了代码重用的能力。 |
难以处理复杂关系和状态:传统的过程式编程很难处理对象之间的复杂关系和状态变化。对于一些复杂的业务问题,过程式编程往往需要大量的代码来维护对象之间的状态,导致代码的可读性和可维护性降低。 | 面向对象编程通过将对象的数据和行为封装在一起,保证了对象状态的一致性和完整性。通过类之间的关系和交互,可以更好地处理对象之间复杂的关系和状态变化。 |
缺乏抽象和封装:过程式编程往往缺乏对问题领域的抽象,代码的可读性差。同时,过程式编程也缺乏对数据和行为进行封装的机制,导致代码容易受到外部的影响。 | 面向对象编程通过类的定义和对象的创建,提供了对问题领域的抽象和建模。同时,封装机制将数据和行为封装在类中,隐藏了内部实现细节,提高了代码的可读性和可维护性。 |
🚀一、面向对象相关概念
🔎1.相关概念
🦋1.1 对象
对象是基本的运行实体,它是类的一个实例。一个对象封装了数据和行为的整体,代表了现实世界中的具体事物,例如学生、汽车等。对象具有明确的边界,定义了自己的行为,并且可以根据需要进行扩展。
举例来说,我们可以创建一个名为"Student"的类,然后通过该类来实例化不同的学生对象。每个学生对象都会有自己的属性(例如姓名、年龄、学号等)和行为(例如上课、考试、提交作业等)。这些属性和行为的封装在对象内部,外部的用户只能通过暴露的接口来访问和操作。这样,每个学生对象都具有清晰的边界和良好定义的行为。
另一个例子是汽车。我们可以创建一个名为"Car"的类,然后通过该类来实例化不同的汽车对象。每辆汽车对象都会有自己的属性(例如品牌、型号、颜色等)和行为(例如启动、加速、刹车等)。这些属性和行为的封装在对象内部,外部的用户只能通过暴露的接口来与汽车对象进行交互。每辆汽车对象都具有清晰的边界和良好定义的行为。
通过对象的封装和可扩展性,我们可以更好地管理和操作这些真实存在的实体,使程序代码更具可读性、可维护性和可扩展性。
🦋1.2 消息
消息是对象之间进行通信的一种构造。类是对象的抽象,它定义了一组大体相似的对象结构,包括实体类、边界类和控制类。实体类用于对必须存储的信息和相关行为建模,它是需要长久保存且一直存在的类。边界类用于系统内部与系统外部的业务主角之间进行交互建模。控制类用于对一个或几个用例所特有的控制行为进行建模,它表示在用例执行过程中被动出现的特定行为。
举例说明:
假设我们要设计一个图书馆管理系统,可以有以下类别:
- 实体类:Book(书籍类,包括属性如书名、作者和出版日期等,以及行为如借阅和归还)、Library(图书馆类,包括属性如馆藏书籍和开放时间等,以及行为如添加书籍和借出书籍);
- 边界类:User(用户类,包括属性如用户名和密码等,以及行为如登录和查看借阅历史)、UI(用户界面类,用于与用户进行交互,包括显示图书馆的书籍列表和接收用户的操作);
- 控制类:Loan(借阅类,用于处理借阅过程中的控制行为,如检查是否有库存、记录借阅历史)、Authentication(认证类,用于处理用户登录的控制行为)。
这些类别之间可以通过消息进行通信。例如,当用户登录时,UI类会向Authentication类发送登录请求消息;当图书馆添加新书时,Library类会向Book类发送添加书籍消息。这样,不同类之间的通信构成了整个图书馆管理系统的功能。
🦋1.3 继承
继承是一种机制,它允许父类和子类之间共享数据和方法。继承是类之间的一种关系,其中子类可以继承(获得)父类的属性和方法,同时可以添加自己独有的属性和方法。
举例说明:假设有一个父类Animal,它有属性name和方法sayHello()。现在有子类Cat和Dog,它们继承了Animal类。通过继承,Cat和Dog类可以拥有name属性和sayHello()方法,并且还可以在自己的类中添加额外的属性和方法。
🦋1.4 多态
多态是指当不同的对象接收到同一个消息时,会产生完全不同的反应。它包括参数多态、包含多态、过载多态和强制多态这四种类型。多态的实现是通过继承机制来支持的。
具体来说,参数多态是指不同类型的参数可以有多种结构类型。例如,在一个图形绘制的程序中,可以有不同类型的图形对象(如圆形、矩形、三角形)作为参数传入一个绘制方法,每种类型的图形对象会通过自己的绘制方式进行绘制。
包含多态是指父类对象可以引用子类对象,通过父类的引用调用子类的方法。例如,有一个动物类作为父类,有猫类和狗类作为子类,可以通过动物类的引用调用子类特有的方法,如发出不同的叫声。
过载多态是指在同一个类中,可以有多个方法名相同但参数类型或个数不同的方法。例如,在一个计算器类中,可以有多个同名的加法方法,分别接收不同类型或个数的参数,实现不同类型的加法运算。
强制多态是指可以通过强制类型转换来实现多态。例如,将一个父类对象强制转换为子类对象,以调用子类特有的方法。
多态使得不同的对象能够根据自己的特性对同一个消息产生不同的反应,提高了代码的灵活性和可扩展性。
🦋1.5 覆盖(重写)
子类通过重写父类的方法,可以在原有父类接口的基础上,用适合于自己要求的实现去替换父类中的相应实现。具体而言,在子类中可以重定义一个与父类同名同参数的方法,并在该方法中实现子类自己的逻辑。
举例来说,假设有一个父类Animal,其中有一个eat()方法用于描述动物的进食行为。现在有一个子类Dog,想要重写eat()方法并定义自己的进食行为。子类Dog可以通过重写父类的eat()方法,在自己的eat()方法中实现狗狗特有的进食行为。
🦋1.6 重载
函数重载和函数覆盖是两个概念需要区分开来。函数重载是指在同一个类中,可以有多个同名函数,但它们的参数类型或个数必须不同。函数重载与子类和父类之间无关,只与函数本身的参数有关。例如,在一个计算器类中,可以有两个同名的add函数,一个接受两个整数参数,另一个接受两个浮点数参数。
函数覆盖(也称为函数重写或方法重写)则是指子类重写了父类中的同名函数。子类覆盖的函数必须与父类的函数具有相同的函数名、返回类型和参数列表。例如,有一个Animal类和它的子类Dog,Animal类中有一个makeSound函数,子类Dog可以重写该函数以发出不同的声音。
函数重载与函数签名有关,可以在同一个类中有多个同名函数,但参数类型或个数必须不同;而函数覆盖则是子类重写了父类中的同名函数,要求函数名、返回类型和参数列表都相同。
🦋1.7 封装
封装是一种信息隐蔽技术,其目的是将对象的使用者和生产者分离。它允许其他开发人员无需了解所要使用的软件组件内部的工作机制,只需知道如何使用组件。封装的好处是可以隐藏实现细节,提高代码的可维护性、可重用性和安全性。
举个例子来说明封装的概念:假设有一个汽车类,内部包含了发动机、轮胎、方向盘等组件。使用封装的思想,我们可以将这些组件的内部工作机制隐藏起来,只提供一个公共接口,让其他开发人员只需要知道如何使用这些组件即可。比如,其他开发人员可以调用汽车类的加速、刹车、转向等方法来控制汽车的运行,而无需了解引擎是如何工作的、轮胎是如何转动的等细节。
通过封装,我们可以实现代码的模块化、屏蔽实现细节,从而提高了代码的可维护性。同时,封装还可以提高代码的可重用性,因为其他开发人员只需要关注如何使用组件,而无需重新编写相同的代码。另外,封装还可以提高代码的安全性,因为隐藏了内部实现细节,其他人无法直接访问和修改。
🦋1.8 静态类型
静态类型是指一个对象的类型在编译时就确定的特性,而动态类型则是指对象类型在运行时才能确定。例如,对于静态类型,我们可以在编译时声明一个变量的类型,并且该变量的类型将在编译时就被确定,而无法在运行时改变。例如,使用静态类型的语言如Java中,我们可以声明一个整数变量int x = 5;,在编译时便确定了x的类型为整数。相比之下,动态类型允许类型在运行时才能确定,可以根据变量的赋值来推断其类型。例如,使用动态类型的语言如Python中,我们可以声明一个变量x = 5,类型会在运行时根据赋值自动确定为整数类型。这种灵活性使得动态类型语言更加适应于快速开发和灵活的编程需求。
🦋1.9 静态绑定(静态分配)
静态绑定(静态分配)是基于静态类型的,这意味着在程序执行之前,方法已经被绑定。这意味着编译器可以根据变量的静态类型来确定调用的方法。例如,假设我们有一个基类Animal和两个子类Dog和Cat,它们都有一个名为"makeSound"的方法。我们创建一个Animal类型的变量a,并将其分别赋值为Dog和Cat的实例。当我们调用a.makeSound()时,由于静态类型是Animal,编译器将选择Animal类中的makeSound方法进行绑定,而不是具体子类的makeSound方法。这就是静态绑定的概念。
🦋1.10 动态绑定
动态绑定是基于动态类型的,运行时根据变量实际引用的对象类型决定调用哪个方法。动态绑定支持多态,即同一个方法名可以有多个不同的实现,根据对象的类型来自动选择正确的方法。
举个例子,假设有一个父类Animal和两个子类Dog和Cat。它们都有一个名为"makeSound"的方法。我们定义一个Animal类型的变量animal,并将其引用指向Dog对象。当我们调用animal.makeSound()时,由于动态绑定的关系,实际上会调用Dog类中的makeSound方法。如果我们改变animal的引用,指向Cat对象,那么调用animal.makeSound()将会调用Cat类中的makeSound方法。这个例子展示了动态绑定的特性,方法的具体实现是在运行时根据对象的类型决定的。
🔎2.面向对象分析
🦋2.1 面向对象分析
面向对象分析是一种方法论,用于确定问题域并深入理解问题。它将问题领域分解为对象,通过审视对象之间的关系和行为,来分析问题的本质和要求。
举个例子,假设我们要设计一个图书管理系统。在面向对象分析过程中,我们会考虑系统中的各种对象,如图书、图书馆、读者等。我们会分析这些对象的属性和方法,了解它们之间的关系。比如,图书对象可能有属性包括书名、作者、出版日期等,方法包括借书、还书等操作。图书馆对象可能有属性包括馆名、地址等,方法包括添加图书、借书记录等操作。通过这样的分析,我们可以更好地理解问题域,从而更有效地设计系统。
🦋2.2 五个活动
包含五个活动:认定对象(按自然存在的实体确定对象)、组织对象(分析对象关系,抽象成类)、对象间的相互作用(描述各对象在应用系统中的关系)、确定对象的操作(操作,如创建增加删除等)、定义对象的内部信息(属性)。
认定对象:通过观察自然存在的实体,确定需要在应用系统中考虑的对象。例如,在一个电商应用中,我们可以认定对象有商品、用户、订单等。
组织对象:分析对象之间的关系,将它们抽象成类。例如,根据商品和用户之间的交互关系,我们可以抽象出商品类和用户类。
对象间的相互作用:描述各对象在应用系统中的关系。例如,在电商应用中,商品和用户之间的相互作用可以是用户浏览商品、将商品添加到购物车等操作。
确定对象的操作:定义对象可以进行的操作,如创建、增加、删除等。例如,用户类可以有创建账号、登录、修改密码等操作。
定义对象的内部信息:定义对象的属性,即对象所具有的特征。例如,商品类可以有名称、价格、库存等属性。
综合例子:在一个社交媒体应用中,我们认定对象有用户、帖子、评论等。根据用户与帖子之间的关系,我们抽象出用户类和帖子类。用户可以浏览帖子、发表评论等,这些为对象间的相互作用。用户类可以有创建账号、修改个人信息等操作。帖子类可以有标题、内容、发布时间等属性。
🦋2.3 面向对象设计
面向对象设计是一种方法论,旨在通过设计分析模型并实现相应的源代码,在目标代码环境中执行这些源代码,以解决特定的设计问题域。举个例子来说明,假设我们正在设计一个图书馆管理系统,我们可以使用面向对象设计来创建图书馆、图书、用户等对象,并定义它们之间的关系和行为。通过面向对象设计,我们可以设计出一个能够方便地管理图书、借还书籍、查询图书等功能的系统。在这个系统中,图书馆、图书、用户等就是面向对象设计中的类,它们的属性和方法就是相应的源代码,而系统的运行环境就是目标代码环境。通过面向对象设计,我们可以实现一个功能完善、易于维护和扩展的图书馆管理系统。
🦋2.4 面向对象程序设计
面向对象程序设计是一种使用面向对象程序设计语言实现设计方案的方式。它的主要思想是将现实世界中的事物抽象为对象,通过定义对象的属性和方法来描述其特征和行为,并通过对象之间的交互来实现系统功能。
举例说明:假设我们要设计一个图书管理系统。面向对象程序设计的思想将图书抽象为一个对象,该对象具有属性(例如书名、作者、出版日期)和方法(例如借书、还书)。我们还可以定义一个用户对象,该对象具有属性(例如姓名、借阅记录)和方法(例如借书、归还书籍)。通过面向对象程序设计语言(如Java、C++)实现图书管理系统,我们可以方便地创建图书对象和用户对象,并通过对象之间的交互实现图书借阅、归还等功能。这样,我们可以在系统中使用面向对象的思想来组织和管理图书的相关操作,提高系统的可扩展性和可维护性。
🦋2.5 面向对象测试
面向对象测试,与普通测试步骤并无不同,可分为四个层次。这四个层次分别是算法层、类层、模板层和系统层。
算法层是指在测试类中定义的每个方法,类似于单元测试。例如,对于一个图书管理系统的测试,可以对添加书籍、删除书籍等方法进行算法层的测试。
类层是指测试同一个类中所有方法与属性的相互作用,特有的模块测试。例如,在图书管理系统中,可以对图书类进行类层测试,测试其方法之间的相互调用以及属性的正确性。
模板层是指测试一组协同工作的类之间的相互作用,类似于集成测试。例如,在图书管理系统中,可以对图书类、图书馆类、读者类等多个类进行模板层测试,测试它们之间的交互是否正常。
系统层是指类似系统测试的测试层次。例如,在图书管理系统中,可以对整个系统进行系统层测试,测试其整体功能是否符合要求。
🔎3.面向对象的设计原则
🦋3.1 单一责任原则
这个原则就是让一个类只做一件事情,不要把太多的任务放在一个类里。这样做的好处是,当你需要修改某个功能时,只需要关注一个类,而不用担心影响其他功能。
举例:想象你正在开发一个学生管理系统。你有一个 Student 类,它负责存储学生的信息,比如姓名和年龄。你还有一个 StudentManager 类,它负责管理学生的添加、删除等操作。这样,每个类只负责一个特定的责任。
举例:想象你是一名学生。你每天要面对多门课程,每门课程都有不同的老师和作业。如果你把所有课程的笔记、作业和书都放在一个文件夹里,当你需要找到特定课程的资料时会变得非常混乱。相反,如果你为每门课程都准备一个专用的文件夹,你就能更轻松地管理和找到所需的信息。每个文件夹就代表了一个类,它们只负责一个特定的任务,即存储与该课程相关的资料。
🦋3.2 开放封闭原则
这个原则意味着你可以扩展现有的代码,但不需要修改已有的代码。你应该允许新功能的添加,而不会影响到已经运行良好的功能。
举例:假设你正在编写一个图形绘制软件,你有一个 Shape 类,代表各种形状。现在,你想添加一个新的形状,比如三角形。你应该能够通过创建一个新的类(例如 Triangle 类),而不是修改已有的 Shape 类。
举例:想象你是一名家庭主妇,你正在准备一顿丰盛的晚餐。你已经在规划中有一些菜肴,但客人可能会有特殊的饮食要求。你可以轻松地加入一个新的菜肴或调整配方,而不会影响到你已经准备好的菜肴。这就是开放封闭原则,你的晚餐计划是“封闭”的,因为已经准备好了,但你可以“开放”地添加新的菜肴,以满足不同的需求。
🦋3.3 里氏替换原则
这个原则强调子类应该能够替换父类而不会影响程序的正确性。换句话说,你应该能够使用子类的实例来替代父类的实例,而不引发错误。
举例:想象你有一个 Bird 类,代表鸟类,其中有一个 fly 方法。现在你派生了一个 企鹅类。根据里氏替换原则,你应该能够在不引发错误的情况下使用 企鹅对象来调用 fly 方法,即使实际上企鹅不会飞。
举例:想象你在一个家庭聚会上,有一个传统的糕点摊位。人们习惯了在那里购买各种类型的糕点。假设你去那里买了一个巧克力蛋糕,但是当你尝试吃它时,却发现它其实是一个水果蛋糕。这就违反了里氏替换原则,因为人们期望能够用巧克力蛋糕替代任何其他类型的蛋糕。但在这个例子中,水果蛋糕并不能真正替代巧克力蛋糕,因为它不是巧克力蛋糕的子类。
🦋3.4 依赖倒置原则
这个原则强调抽象应该依赖于细节,而不是相反。高层模块不应该直接依赖于低层模块的细节,而应该通过抽象进行交互。
举例:假设你正在开发一个电子商务平台。你有一个 OrderProcessor 类负责处理订单。而这个类不应该直接依赖于具体的支付方式,而是依赖于一个抽象的 PaymentGateway 接口。这样,你可以轻松地更改支付方式,而不必修改 OrderProcessor。
举例:想象你是一名旅行者,你需要租一辆车去探索一个城市。你不需要亲自去了解车子的每个零件如何工作,你只需要知道如何使用它们。租车公司为你提供了一辆可用的车,而不是让你去修理引擎或更换轮胎。在这个例子中,你是高层模块,租车公司是低层模块,你依赖于租车公司提供的抽象服务,而不是直接与车辆细节打交道。
🦋3.5 接口分离原则
这个原则强调客户端不应该被强制依赖它们不需要的方法。接口应该只包含客户端需要的方法,避免造成冗余和不必要的复杂性。
举例:想象你正在设计一个媒体播放器。你应该根据功能拆分成不同的接口,如 AudioPlayer 和 VideoPlayer。这样,如果你只需要一个音频播放器,你就不会被迫实现视频播放相关的方法,从而遵循了接口分离原则。
举例:假设你正在考虑加入一个运动俱乐部。你有多个选项可供选择,如游泳、篮球和瑜伽。不同的人有不同的兴趣,你可能只想参加其中一种活动。运动俱乐部应该将这些活动分开成不同的项目,以便每个人只关注他们感兴趣的部分。这样,你不需要强制自己参加所有的活动,而是可以选择与你有兴趣的活动接口。
🚀感谢:给读者的一封信
亲爱的读者,
我在这篇文章中投入了大量的心血和时间,希望为您提供有价值的内容。这篇文章包含了深入的研究和个人经验,我相信这些信息对您非常有帮助。
如果您觉得这篇文章对您有所帮助,我诚恳地请求您考虑赞赏1元钱的支持。这个金额不会对您的财务状况造成负担,但它会对我继续创作高质量的内容产生积极的影响。
我之所以写这篇文章,是因为我热爱分享有用的知识和见解。您的支持将帮助我继续这个使命,也鼓励我花更多的时间和精力创作更多有价值的内容。
如果您愿意支持我的创作,请扫描下面二维码,您的支持将不胜感激。同时,如果您有任何反馈或建议,也欢迎与我分享。
再次感谢您的阅读和支持!
最诚挚的问候, “愚公搬代码”
- 点赞
- 收藏
- 关注作者
评论(0)