【设计模式】面向对象的设计原则

news2025/1/16 5:42:06

(一) UML 和面向对象设计原则

1. 一种某唱片播放器不仅可以播放唱片,而且可以连接计算机并把计算机中的歌曲刻录到

唱片上(同步歌曲)。连接计算机的过程中还可自动完成充电。

关于唱片,还有如下描述信息:

(1) 每首歌曲的描述信息包括:歌曲的名字,谱写这首歌曲的艺术家以及演奏这首

歌曲的艺术家。只有两首歌曲的这三部分信息完全相同时,才认为它们是同一

首歌曲。艺术家可能是一名歌手或一支由 2 人或 2 名以上的歌手组成的乐队。

一名歌手可以不属于任何乐队,也可以属于一个或多个乐队。

(2) 每章唱片有多条音轨构成;一条音轨中只包含一首歌曲或为空,一首歌曲可分

布在多条音轨上,通一首歌曲在一张唱片中最多只能出现一次。

(3) 每条音轨都有一个开始位置和持续时间。一张唱片中音轨的次序是非常重要的,

因此对于任意一条音轨,播放器需要准确地指导它的下一条音轨和上一条音轨

是什么(如果存在的话)。

根据上述描述,采用面向对象方法对其进行分析或设计,得到了如表 1-3 所示的

类列表和如图 1-16 所示的初始类图。

表 1-3 类列表

类名 说明 类名 说明

Artist 艺术家 Musician 歌手

Song 歌曲 Track 音轨

Band 乐队 Album 唱片

【问题 1】根据题干中的描述,使用表 1-3 给出的类的名称,给图 1-16 中 A~F 所对应的类。

【问题 2】根据题干中的描述,给出图 1-16 中(1)~(6)处的多重性。

【问题 3】图 1-16 中缺少了一条关联,请指出这条关联两端所对应的类以及每一端的多重

性。

2. 在某绘图软件中提供了多种大小不同的画笔(Pen),并且可以给画笔指定不同颜色,某

设计人员针对画笔的结构设计了如下类图:

通过仔细分析,设计人员发现该类图存在非常严重的问题,如果需要增加一种新的大

小的笔或者增加一种新的颜色,都需要增加很多子类,如增加一种绿色,则对应每一种大小

的笔都需要增加一支绿色笔,系统中类的个数急剧增加。

试根据依赖倒转原则和合成复用原则对该设计方案进行重构,使得增加新的大小的笔

和增加新的颜色都较为方便。

本练习可以通过依赖倒转原则和合成复用原则进行重构,重构方案如下所示:

    在本重构方案中,将笔的大小和颜色设计为两个继承结构,两者可以独立变化,根据依赖倒转原则,建立一个抽象的关联关系,将颜色对象注入到画笔中;再根据合成复用原则,画笔在保持原有方法的同时还可以调用颜色类的方法,保持原有性质不变。如果需要增加一种新的画笔或增加一种新的颜色,只需对应增加一个具体类即可,且客户端可以针对高层类Pen和Color编程,在运行时再注入具体的子类对象,系统具有良好的可扩展性,满足开闭原则。(注:本重构方法即为桥接模式,在第4章将对该模式进行进一步讲解并提供实例代码来实现该模式。

也可以进一步将Size进行和Color一样的处理,单独封装,继承产生子类,最后组合到Pen当中。

  1. 结合面向对象设计原则分析:正方形是否是长方形的子类?

根据面向对象设计中的Liskov Substitution Principle(里氏替换原则),如果正方形是长方形的子类,则正方形应该可以替换长方形在任何情况下使用,而不会导致错误或异常。

然而,在现实世界中,一个正方形具有长和宽两个相等的边,而长方形具有两组相等的对边。因此,正方形和长方形之间存在本质上的差异,不能完全等同。例如,如果我们编写了一个函数,该函数需要接受一个长方形作为参数,并根据长方形的参数计算面积。如果我们将一个正方形作为参数传递给该函数,则可能会产生错误的结果,因为正方形的两条边可能被错误地视为不同的值,并导致面积计算错误。

因此,可以得出结论:正方形不应该是长方形的子类,因为它们的属性和行为不完全相同,并且不满足LSP原则。虽然正方形和长方形之间存在继承关系,但这种关系是不合适的。正确的方法是将它们定义为两个独立的类,每个类应该只定义适用于自己的属性和方法。

长方形和正方形之间的关系可以通过共同继承一个抽象类或接口来表示,该抽象类或接口定义共同的属性和行为。例如,我们可以定义一个名为“四边形”的抽象类或接口,其中包含一个计算面积的方法。然后,我们可以创建一个名为“长方形”的类和一个名为“正方形”的类,它们都继承自“四边形”类或实现“四边形”接口,并且在自己的类中实现各自对应的属性和方法。

这样设计既满足了开闭原则(应用程序对扩展开放,对修改关闭),也遵循了里氏替换原则,即子类可以完全替代父类。因为“长方形”和“正方形”都是“四边形”的子类,所以可以将它们视为同一种类型的对象,而不会导致任何错误或异常

4. 在某公司财务系统的初始设计方案中存在如图 1-2 所示的 Employee 类,该类包含员工

编号(ID)、姓名(name)、年龄(age)、性别(gender)、薪水(salary)、每月工作时数

(workHoursPerMonth)、每月请假天数(leaveDaysPerMonth)等属性。该公司的员工包括

全职和兼职两类,其中每月工作时数用于存储兼职员工每个月工作的小时数,每月请假天数

用于存储全职员工每个月请假的天数。系统中两类员工计算工资的方法也不一样,全职员工

按照工作日数计算工资,兼职员工按照工作时数计算工资,因此在 Employee 类中提供了两

个方法 calculateSalaryDays()和 calculateSalaryHours(),分别用于按照天数和时数计算工

资,此外,还提供了方法 displaySalary()用于显示工资。

试采用所学面向对象设计原则分析图 1-2 中 Employee 类存在的问题并对其进行重构,绘制重构之后的类图。

根据所提供的问题描述,对图1-2中的Employee类进行分析,存在以下问题:

单一职责原则:Employee类承担了过多的职责,包括保存员工信息、计算工资和显示工资等。应该将这些不同的职责进行解耦和分离。

开放封闭原则:系统应该对扩展开放,对修改封闭。但是在原设计中,新增不同类型员工时,需要修改Employee类的方法来适应不同的计算工资方式,违反了开放封闭原则。

继承关系:全职员工和兼职员工都是员工的特殊类型,但是在原设计中并没有明确体现出继承关系。

基于上述问题,可以进行如下的重构:

创建一个基类Employee,包含共同的属性(员工编号、姓名、年龄、性别)和方法(显示员工信息等)。

在Employee类中,定义一个抽象方法calculateSalary()用于计算工资。不再区分全职员工和兼职员工的计算方式,而是将计算工资的逻辑留给具体的子类实现。

创建两个子类:FullTimeEmployee(全职员工)和PartTimeEmployee(兼职员工)。这两个子类继承Employee类,并实现calculateSalary()方法来根据自己的特定规则计算工资。

在FullTimeEmployee类中添加一个新属性leaveDaysPerMonth,用于存储全职员工每个月请假的天数。

在PartTimeEmployee类中添加一个新属性workHoursPerMonth,用于存储兼职员工每个月工作的小时数。

通过这样的重构,可以解决原设计中违反单一职责原则和开放封闭原则的问题,并明确了全职员工和兼职员工的继承关系。

重构后的类图如下所示:

问题分析

在某公司财务系统的初始设计方案中存在一个名为Employee类的类,它包含员工编号(ID)、姓名(name)、年龄(age)、性别(gender)、薪水(salary)、每月工作时数(workHoursPerMonth)、每月请假天数(leaveDaysPerMonth)等属性。该公司的员工有两种类型:全职和兼职,其中每月工作时数用于存储兼职员工每个月工作的小时数,每月请假天数用于存储全职员工每个月请假的天数。根据员工类型的不同,计算工资的方法也不一样。全职员工按照工作日数计算工资,兼职员工按照工作时数计算工资。因此,在Employee类中提供了两个方法calculateSalaryDays()和calculateSalaryHours(),分别用于按照天数和时数计算工资。此外,还提供了方法displaySalary()用于显示工资。

我们需要使用面向对象设计原则分析Employee类存在的问题,并对其进行重构,绘制重构后的类图。

解决方案

单一职责原则(SRP) - Employee类违反SRP,因为它具有太多的职责,如存储员工数据、计算工资和显示工资等。因此,我们可以为每个职责创建单独的类,以实现高内聚和低耦合。

开闭原则(OCP) - Employee类没有任何抽象或接口,因此违反了OCP。因此,我们需要使用抽象或接口来允许扩展而无需修改源代码。

里氏替换原则(LSP) - Employee类不违反LSP,因为它是一个具体类,没有继承关系。

接口隔离原则(ISP) - Employee类违反ISP,因为其方法calculateSalaryDays()、calculateSalaryHours()和displaySalary()之间没有关联。因此,我们可以根据功能将这些方法分成不同的接口。

依赖倒置原则(DIP) - Employee类违反DIP,因为它依赖于低级模块(薪资计算)和高级模块(显示薪资)。因此,我们可以创建抽象或接口来减少这些模块之间的耦合。

根据这些原则,我们可以重构Employee类如下:

创建一个基本接口IEmployee,其中声明三个方法:getSalary()、getName()和getID()。

创建两个具体类FullTimeEmployee和PartTimeEmployee,它们都继承自IEmployee。FullTimeEmployee类将有一个属性用于存储每月请假天数,而PartTimeEmployee类将有一个属性用于存储每月工作的小时数。

为基于工作日和工作时数计算工资的方法分别定义两个单独的接口IDaysSalaryCalculator和IHoursSalaryCalculator。这些接口将具有一个方法calculateSalary(),该方法以FullTimeEmployee或PartTimeEmployee实例作为输入,并返回计算出的工资作为输出。

在一个具体类DaysSalaryCalculator中实现IDaysSalaryCalculator接口,用于计算每月请假天数的全职员工的薪水。

在一个具体类HoursSalaryCalculator中实现IHoursSalaryCalculator接口,用于计算每月工作时数的兼职员工的薪水。

创建一个SalaryDisplay类,它具有一个方法displaySalary(IEmployee employee),用于显示任何类型的员工的薪水。

5. 在某图形库 API 中提供了多种矢量图模板,用户可以基于这些矢量图创建不同的显示

图形,图形库设计人员设计的初始类图如下所示:

在该图形库中,每个图形类(如 Circle、Triangle 等)的 init()方法用于初始化所创建的图形,

setColor()方法用于给图形设置边框颜色,fill()方法用于给图形设置填充颜色,setSize()方法

用于设置图形的大小,display()方法用于显示图形。

客户类(Client)在使用该图形库时发现存在如下问题:

(1) 由于在创建窗口时每次只需要使用图形库中的一种图形,因此在更换图形时需要修

改客户类源代码;

(2) 在图形库中增加并使用新的图形时需要修改客户类源代码;

(3) 客户类在每次使用图形对象之前需要先创建图形对象,有些图形的创建过程较为复

杂,导致客户类代码冗长且难以维护。

现需要根据面向对象设计原则对该系统进行重构,要求如下:

(1) 隔离图形的创建和使用,将图形的创建过程封装在专门的类中,客户类在使用图形

时无须直接创建图形对象,甚至不需要关心具体图形类类名;

(2) 客户类能够方便地更换图形或使用新增图形,无须针对具体图形类编程,符合开闭

原则

正确答案: 

本练习可以通过单一职责原则和依赖倒转原则进行重构,具体过程可分为如下两步:

(1) 由于图形对象的创建过程较为复杂,因此可以将创建过程封装在专门的类中(这种专门用于创建对象的类称为工厂类),将对象的创建和使用分离,符合单一职责原则;

(2) 引入抽象的图形类Shape,并对应提供一个抽象的创建类,将具体图形类作为 Shape的子类,而具体的图形创建类作为抽象创建类的子类,根据依赖倒转原则,客户端针对抽象图形类和抽象图形创建类编程,而将具体的图形创建类类名存储在配置文件中。

重构之后的类图如下所示:

 

    通过上述重构,在抽象类Creator中声明了创建图形对象的方法create(),在其子类中实现了该方法,用于创建具体的图形对象。客户端针对抽象Creator编程,而将其具体子类类名存储在配置文件config.xml中,如果需要更换图形,只需在配置文件中更改Creator的具体子类类名即可;如果需要增加图形,则对应增加一个新的Creator子类用于创建新增图形对象,再修改配置文件,在配置文件中存储新的图形创建类类名。更换和增加图形都无须修改源代码,完全符合开闭原则。(注:本重构方法即为工厂方法模式,在第3章将对该模式进行进一步讲解并提供实例代码来实现该模式。)

简答题]

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

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

相关文章

ChatGPT底层架构Transformer技术及源码实现(一)

ChatGPT底层架构Transformer技术及源码实现 Language Model底层的数学原理之最大似然估计MLE及最大后验概率MAP内部机制详解 Gavin大咖微信:NLP_Matrix_Space 传统人工智能算法的真相(The Truth Under Traditional AI Algorithms),传统人工智能算法是相对于贝叶斯(Bayesia…

【软件设计师暴击考点】程序设计语言-高频考点暴击系列

👨‍💻个人主页:元宇宙-秩沅 👨‍💻 hallo 欢迎 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍💻 本文由 秩沅 原创 👨‍💻 收录于专栏:软件…

碳中和城市建筑能源系统(4):储能篇(龙惟定)2022

碳中和城市建筑能源系统(4):储能篇 摘要 本文是碳中和城市建筑能源系统系列文章的第四篇。在碳中和语境下,无论是增加可再生能源应用的渗透率,还是平抑负荷、提高电网的灵活性,都离不开储能。本文介绍了当今储能技术的主要类型,…

【新星计划·2023】Centos 7安装教程(一步一图)

作者:Insist-- 个人主页:insist--个人主页 作者会持续更新网络知识和python基础知识,期待你的关注 目录 一、下载VMware 二、下载镜像的方式 三、安装Linux ’前言 本文将讲解下载VMware和下载镜像的方式,以及安装centos 7的教…

webpack编译打包从入门到放弃

写在前面的话:推荐学习vite。当然,我更推荐你直接上手体验webpack_demo与vite_demo 看看他们的编译、打包、热更新速度等差距。你也可以直接通过vite开发lib库,一句话就是比webpack快,它有的vite都有,并且更好&#x…

NCI-NFCEE

10.5 NFCEE 状态 NFCC 使用此控制消息向 DH 通知启用的 NFCEE 状态的变化。 NFCC 发送 NFCEE_STATUS_NTF 来报告启用的 NFCEE 状态的变化。 对于任何禁用或无响应的 NFCEE,NFCC 不应发送 NFCEE_STATUS_NTF。 当启用 NFCEE 并且 NFCC 检测到与该 NFCEE 通信时存在不…

C++布隆过滤器

目录 布隆过滤器介绍实现哈希函数布隆过滤器删除 小结使用——题目 布隆过滤器 介绍 在许多场景下,如设置昵称时,往往要求唯一性。这时就需要高效判断该昵称是否被使用过。 使用红黑树的kv模型或者哈希表来组织昵称集合,可以,但缺…

Qt中的日期和时间

目录 QDate 示例(打印年月日): QTime 示例(显示时分秒): QDateTime 示例(显示当前日期和时间): 示例(分别取出 年 月 日 时 分 秒)&#xff…

牛客网专项练习——C语言错题集(8)

文章目录 字符串拼接和拷贝while 与 fortypedef 和 define浮点类型的组成部分 字符串拼接和拷贝 这题并没有难度,但不知为什么我把该题空着。 strcpy 用于拷贝字符串,strcat 用于拼接字符串。 while 与 for 假如 i 0,while 循环里 s1 被执…

【AI机器学习入门与实战】机器学习算法都有哪些分类?

👍【AI机器学习入门与实战】目录 🍭基础篇 🔥 第一篇:【AI机器学习入门与实战】AI 人工智能介绍 🔥 第二篇:【AI机器学习入门与实战】机器学习核心概念理解 🔥 第三篇:【AI机器学习入…

Z变换方程转化为差分方程

将Z变换方程转换为差分方程的过程称为反Z变换。反Z变换是将信号从复频域转换为时间域的过程。如果我们已知一个系统的传递函数,即Z变换方程: H ( z ) Y ( z ) X ( z ) b n b n − 1 z − 1 ⋯ b 0 z − n 1 a n a n − 1 z − 1 ⋯ a 0 z − n 0…

AVL 树

目录 AVL树的概念AVL树节点的定义AVL树的插入AVL树的旋转左单旋(parent->_bf 2 && cur->_bf 1)a,b,c当高度为0a,b,c当高度为1a,b,c当高度为2a,b,c当高度为...... 右单旋(parent->_bf -2 && cur->_bf -1)a,b,c当高度为0a,b,c当高度为1a,b,c当高…

强化学习从基础到进阶-案例与实践[4]:深度Q网络-DQN、double DQN、经验回放、rainbow、分布式DQN

【强化学习原理项目专栏】必看系列:单智能体、多智能体算法原理项目实战、相关技巧(调参、画图等、趣味项目实现、学术应用项目实现 专栏详细介绍:【强化学习原理项目专栏】必看系列:单智能体、多智能体算法原理项目实战、相关技巧…

Azkaban初认识

Azkaban初认识 文章目录 Azkaban初认识Azkaban是什么?为什么需要工作流调度系统?常见的工作流调度系统Azkaban 与 Oozie的对比 Azkaban是什么? Azkaban是一个开源的分布式工作流管理器,在LinkedIn实施,以解决Hadoop作业…

RT-Thread-03-栈空间分配

栈空间分配 线程状态转换图: 系统滴答时钟 每个操作系统都存在一个系统时钟,是操作系统中最小的时钟单位。这个时钟负责系统和时间相关的一些操作。这个时钟由硬件定时器的定时中断产生。 系统时钟的频率需要根据芯片的处理能力来决定, 频…

【MySQL基础 | 第一篇】数据处理之基本查询

前言 查询语句属于DML(Data Manipulation Language)数据操作语言的其中一种,用于从数据库中提取所需的数据。通过灵活的条件和组合,查询语句帮助用户有效地获取、过滤和排序数据,满足各种信息需求。 文章目录 前言1️⃣…

团体程序设计天梯赛-练习集L1篇⑨

🚀欢迎来到本文🚀 🍉个人简介:Hello大家好呀,我是陈童学,一个与你一样正在慢慢前行的普通人。 🏀个人主页:陈童学哦CSDN 💡所属专栏:PTA 🎁希望各…

编译原理笔记17:自下而上语法分析(4)LR(0)、SLR(1) 分析表的构造

目录 LR(0) 文法LR(0) 分析表的构造例 SLR(1) 文法SLR 分析表构造 非 SLR(1) 文法举例二义文法都不是 SLR(1) 文法不是二义文法的非 SLR(1) 文法 LR(0) 文法 若一个文法 G 的拓广文法 G’ 的识别活前缀的自动机中的每个状态(项目集)均不存在下述情况&…

【一文通】C/C++与Go语言混合编程入门级教程(Windows平台完成)

一、概述 Go语言可以通过自带的 cgo 工具进行 CGO 混合编程,这个工具放在go安装目录的 pkg\tool 下,其源代码则在 src\runtime\cgo 里面,当然作为入门教程本文不打算对cgo的实现原理进行深入研究,仅从 Hello World 的角度来实际体…

快速查询银行卡发卡省市和归属银行,了解自己的财务状况!

API接口是现代软件开发的基本组成部分。它们允许应用程序通过互联网连接到其他软件系统,并从这些系统中获取或传输数据。银行卡归属地查询API接口是为开发人员提供的一种工具,可以帮助他们轻松地查询银行卡的归属地信息。在本文中,我们将介绍…