ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

面向对象设计复习指南:从类图到设计原则一次搞定

面向对象设计复习指南:从类图到设计原则一次搞定 软件工程复习偏偏就卡在第七章这事我见得太多了。前两天还有学生揣着笔记本问我面向对象设计到底该怎么复习怎么一看到类图就发懵明明上课听懂了做起题来还是不知道怎么下手。其实这一章没有想象中那么玄。你只要抓住一条主线——从分析阶段已经确认的“对象”出发把对象之间的关系、职责、交互方式设计清楚整章内容基本就串起来了。这篇复习笔记围绕软件工程课程里最常见的那套教材体系来写主要讲清“面向对象的设计方法”的底层逻辑、常用步骤、高频考点以及复习时最容易踩的坑适合正在准备期末考、考研专业课或者想把自己课程设计代码改得规范一点的同学参考。有些人复习这章喜欢死背概念背到“对象是现实世界实体的抽象”“类是对象的模板”就以为完成任务了结果一到画类图、一到写设计说明完全不知道从哪动笔。这种现象太普遍了根本不是你不努力而是没有把“知识”转化成“操作流程”。所以这篇文章不打算只罗列定义我会拿真实课程设计里最常见的图书管理系统当例子把从需求到类图、从职责划分到消息协作的每一步都拆开讲一遍再配上考试里容易被扣分的细节帮你把这本书从“读过”变成“会用”。1. 这一章到底在讲什么——先建立整体地图1.1 为什么“面向对象设计”是软件工程课的一座分水岭软件工程课程前半段基本都在讲传统方法可行性研究、需求分析、概要设计、详细设计再配合数据流图、数据字典、结构化语言这些工具整体思路是“自顶向下、逐步分解、功能模块化”。等到了第七章教材突然切换成“对象”“类”“封装”“继承”“多态”“消息”这一整套新词汇很多同学就是在这一步开始跟不上的。其实把视角拉远一点看面向对象设计方法之所以被单独成章是因为它解决的是软件系统“怎么构造才稳定、才容易改”的问题。结构化设计把系统拆成一个个函数和模块模块之间通过参数传递数据面向对象设计则把数据和操作数据的行为打包成对象对象之间通过消息进行协作。一个是功能中心主义一个是数据/对象中心主义这是两种完全不同的世界观。从考试角度来想第七章的复习重点是三类问题概念辨析题比如聚合和组合有什么区别、设计判断题比如某个设计违反了哪个原则、综合设计题给你一段需求让你画类图、写关键类设计。如果你能把这一章的框架理清楚后续涉及实现、测试、维护的内容都会好理解很多因为它其实是整条面向对象路线的设计源头。1.2 面向对象设计到底要“设计”什么——别把分析和设计混在一起教材里强调得最多的一个观点是面向对象分析OOA关注“做什么”面向对象设计OOD关注“怎么做”。听起来很清楚但到了实际项目里分析与设计的界限往往非常模糊。我的理解是分析阶段重点是把现实世界里的概念识别出来比如说图书、读者、借阅记录、管理员而设计阶段重点是决定这些概念在软件系统内部怎么表示、怎么协作比如说图书类的属性应该有哪些允许谁去修改图书状态借书这个操作是读者发起的还是管理员发起的又是由哪个类来具体执行。这里有一个我自己复习时摸索出来的套路分析阶段可以先随便写名词把所有能想到的概念都列出来不用管它合理不合理设计阶段再做三件事——筛选名词把明显属于系统内部实现的词挑出来定职责明确每个类应该干什么、不应该干什么定关系把类与类之间的关联、依赖、继承关系画出来。你只要在笔记里把这个流程写清楚考试时就算遇到没见过的题目也不会完全没思路因为你至少知道先干什么、后干什么。2. 面向对象设计的核心准则——考试和上机都绕不开2.1 内聚与耦合判断设计水平的第一把尺几乎每个版本的软件工程教材在讲到设计方法时都会强调“高内聚、低耦合”这六个字。很多同学把这当成口号背下来就完了但考试题目往往不会直接问“什么是高内聚低耦合”而是会给你一段类图让你指出哪个类的设计不合理。这时候你要能具体说出这个类既负责数据库读写又负责界面显示还负责业务逻辑职责太多内聚度低或者这个类直接依赖了另一个类的内部字段类与类之间绑定太紧耦合度高。内聚描述的是一个类内部各个元素之间的紧密程度。一个类里的方法围绕同一个职责转这就是高内聚。举个例子图书类里有方法“借出”“归还”“查询状态”这些都围绕“图书状态”这一个主题内聚就很高如果图书类里突然冒出一个“发送逾期催还短信”的方法这个方法跟图书本身的状态有关但真正的发送逻辑更应该在独立的通知类里放在图书类里虽然也能用却会让类变得混乱。耦合描述的则是类与类之间互相依赖的程度。理想情况下一个类发生修改时尽量不影响其他类如果随便改一个属性类型就能引发一连串连锁修改那耦合就过高了。考试答题时我建议你养成一个习惯凡是让你分析类图设计好不好的题目先写“高内聚、低耦合”这个总原则再具体指出哪个类内聚低、哪两个类耦合高最后给出改进方法。这个答题模板在期末考和考研里都很好用因为阅卷看的是踩分点而不是你的文采。2.2 从原则到实践开闭、里氏、依赖倒置怎么理解除了内聚耦合第七章还会出现面向对象设计原则的内容。不同教材列出的原则数量不太一样但考试出现频率最高的有三个开闭原则、里氏替换原则、依赖倒置原则。这三个原则如果只背定义很快就会忘我建议你每个原则配一个小例子来记忆。开闭原则说“对扩展开放对修改关闭”通俗讲就是不修改已有代码也能增加新功能。比如图书馆系统最初只支持按书名查询现在要增加按作者查询。如果设计时查询模块依赖一个“查询接口”新增查询方式就是新增一个实现类原来代码不动这符合开闭原则如果每增加一种查询就改一遍最外层控制器那就违背了开闭原则。现实项目里“完全不修改”很难做到但这个原则的价值在于引导你把容易变化的部分抽象出来。里氏替换原则说凡是能使用父类的地方都能透明地换成子类。用生活中的例子理解如果“动物”类有“吃”这个方法“猫”继承动物并重写了“吃”那么所有接受“动物”对象的函数都应该能接受“猫”对象而且行为符合预期。考试经常会考子类继承父类后重写方法破坏父类原有约束的情况这时候你要能判断出它违反了里氏替换原则。依赖倒置原则说高层模块不应该依赖低层模块两者都应依赖抽象抽象不应依赖细节细节应依赖抽象。说白了就是业务逻辑不要直接new一个具体数据库实现而是依赖一个数据访问接口。我常说这样一句话面向对象设计的大部分功夫都花在“找接口”上。你复习的时候每看到一个类都要习惯性想一想这个类以后可能变化吗变化的部分要不要抽象成接口这种思维一旦形成设计题就是送分题。3. 从分析模型到设计模型设计步骤拆开揉碎3.1 第一步确定类和对象分清实体、边界、控制类面向对象设计的第一步一般教材都会让你把分析阶段得到的分析模型细化成设计模型其中最先做的事就是确定系统里的类。但我见过太多同学直接把需求里的所有名词都当成类画出来的图有几十个类看上去很丰富实际上根本没法实现。这里有一个比较实用的分类法把类分成实体类、边界类、控制类三种分别对应数据、交互、逻辑三个不同层次。实体类用来描述系统里需要长期保存的信息比如图书、读者、借阅记录。边界类用来描述系统与外部角色之间的交互比如控制台界面、网页接口、短信通知接口。控制类用来协调实体类和边界类之间的业务逻辑比如借书流程控制器、罚款计算器。以图书管理系统为例如果需求描述里反复出现“管理员登录后可以新增图书”那么“登录”这件事可以交给一个登录控制器控制类“管理员信息”和“图书信息”就是实体类“登录界面”就是边界类。画类图的时候我自己习惯先列实体类再列边界类最后补控制类因为实体类最稳定边界类和控制类都是围绕实体类转的。考试时如果时间紧张优先把实体类的属性和方法写全因为实体类往往是得分点最密集的地方。3.2 第二步设计属性与方法的可见性——不是所有字段都要公开确定了有哪些类之后下一步是给每个类设计属性成员变量和方法成员函数并且明确可见性。这里的visible对很多人来说就是public、private的选择题其实它背后隐藏的是封装思想一个类对外只暴露必要的操作接口内部细节要藏起来。设计属性时一般原则是“属性私有方法公有”但这不绝对。如果某个子类确实需要访问父类的保护属性可以用protected如果某个方法只在本类内部使用就应该设计成private。属性设计有个容易忽略的细节属性的类型选择。考试不一定考具体类型但你在笔试题里写“图书编号String”“出版年份int”比只写“图书编号、出版年份”要显得更专业也说明你确实在“设计”而不是“画名词”。方法设计更关键接口方法的命名要尽量用动词开头比如“借出borrow”“归还return”“查询可借状态isAvailable”参数列表要尽量精简一个方法能少传参数就少传参数因为参数越多调用方和实现方的耦合就越重。我在批改学生作业时见过一个很典型的问题某个类的所有属性全是public外部程序随便改完全没有任何校验。这种设计虽然在小型作业里能跑通但放到真实项目里非常危险。比如读者类里的“可借数量”被直接改成了负数后面再查就全错了。所以设计属性时不光要写类型和可见性还要想一想“这个属性允许被谁改、在什么条件下改”限制得越清楚封装越到位。3.3 第三步确定对象间的关联、聚合与组合——最容易被扣分的细节类与类之间的关系是面向对象设计里最容易考、也最容易错的部分。教材里通常会讲关联、聚合、组合、继承、依赖这几种关系这里我挑三个必须搞懂的讲。关联就是类与类之间有静态的结构性联系通常用一条直线表示。比如“读者”和“借阅记录”一个读者可以有多条借阅记录这就是一对多关联。关联还要注意方向单向关联和双向关联在实现上有很大区别设计时应优先考虑单向因为双向会带来额外的维护成本。聚合和组合都是“整体-部分”关系但生命周期不同。聚合是弱关系整体不存在了部分仍然可以单独存在组合是强关系整体不存在了部分也就没有意义了。拿“班级”和“学生”来说班级解散了学生个体还在这是聚合拿“订单”和“订单项”来说订单被删除订单项自然也不可能单独存在这是组合。考试经常出这种判断题你只要抓住“整体没了部分是否还要保留”这个标准基本不会错。继承关系大家都不陌生但设计时要特别注意不要为了代码复用而滥用继承。如果两个类之间没有真正的“is-a”关系只是有一些相同的方法正确的做法应该是抽取公共接口或者用组合代替继承。我自己复习时总结了十六个字“能用组合少用继承能用接口少用抽象类。”这十六个字在很多设计题里都能用上。3.4 第四步细化消息协作和顺序图——把“死”的类变成“活”的系统类图画出来以后系统还是静态的。真正让系统运转起来的是对象之间的消息传递也就是一个对象调用另一个对象的方法。这一步在教材里会用交互图、协作图或顺序图来表示。复习的时候我建议重点掌握顺序图因为顺序图能清晰表达时间先后顺序考试也最容易考。画顺序图的顺序一般是先写出一个具体的业务场景比如“读者借书成功”这个场景然后从发起者开始沿着时间轴往下画每一步谁给谁发消息、消息带什么参数、返回什么都写清楚。借书这个场景大致是读者把借书卡和图书信息提交给界面界面把请求转发给借书控制器控制器去校验读者状态和图书状态校验通过后创建或更新借阅记录最后返回借阅成功信息。顺着这个过程你会自然发现一些问题校验逻辑应该放在哪个类里是对的还是借阅记录类来负责这就是设计阶段需要反复迭代的地方。我常跟学生说类图和顺序图是相互校验的先画哪个都行但最后必须能对上。如果顺序图里出现的某个操作在类图里找不到对应方法说明类图不完整如果类图里的某个方法在顺序图里一次都没出现那这个方法可能设计得没有意义。4. 与面向数据流设计方法的对照——高频对比题怎么答4.1 面向数据流设计的基本套路软件工程教材在介绍面向对象设计之前通常会先讲传统的结构化设计方法其中最核心的就是面向数据流的设计。它的基本思路是先画出数据流图DFD把系统看成数据的流动过程再根据数据流的特征把数据流图映射成软件结构图。映射有两种典型形式变换分析和事务分析。变换分析适用于线性数据处理流程事务分析适用于根据输入选择不同处理路径的流程。很多同学会觉得这部分和第七章关系不大但教材和考试偏偏喜欢把两种方法放在一起比较。你要理解面向数据流设计关注的是“数据从输入到输出经历了哪些变换每个变换对应哪个模块”也就是说设计的基本单位是模块模块按功能划分面向对象设计关注的是“有哪些对象对象之间怎么协作”设计的基本单位是对象对象把数据和功能封装在一起。这两种方法没有绝对的好坏只是在面对不同规模、不同变化需求的系统时表现不一样。结构化方法直观、上手快适合需求稳定、流程清晰的小型系统面向对象方法更适合需求变化频繁、业务规则复杂、需要长期维护的大型系统。考试如果问“为什么现在越来越多项目选择面向对象设计方法”你就从可维护性、可扩展性、对现实世界的模拟这几个角度答。4.2 一张表搞定对比题——答题别再东一句西一句对比题最怕答得零散。我自己复习时做了一张对照表考试时直接“背表格”就能保证不丢点比较维度面向数据流设计结构化设计面向对象设计基本单位模块/函数对象/类核心关注数据变换与处理流程对象职责与对象协作设计起点数据流图DFD分析模型、用例、类图典型映射变换分析/事务分析职责分配、关系建模数据与操作数据与操作分离数据与操作封装在一起扩展方式修改/新增模块新增类、通过接口扩展维护难度模块间耦合往往较高封装与低耦合降低影响范围适用场景需求稳定、流程固定需求多变、业务复杂这张表的每一行都可以单独拿出来做简答题也可以用来做综合分析题的开头框架。答题时切忌只写“一个面向数据一个面向对象”这种空话一定要带上“基本单位”“封装性”“可扩展性”这些术语阅卷老师才能一眼看到你的踩分点。4.3 考试为什么要爱考这两种方法的对比你可能觉得对比题很烦其实它有一个很重要的考察目的检验你是否真正理解了“设计方法”这四个字的内涵。设计方法是连接需求与实现的桥梁你选哪种方法决定了后续代码长成什么样也决定了将来维护时改代码的难度。面向数据流的设计方法本质上更接近早期软件开发的做法优点是把大问题拆成小问题但拆完之后模块之间的接口往往比较复杂数据流向一变模块结构就要跟着变。面向对象设计则把“稳定不变的实体”作为设计中心把“容易变化的规则”通过多态、接口等方式隔离出去所以当需求变化时不需要把整个结构推倒重来。这部分如果你能在答题里写出一套完整的逻辑链条——需求变了结构化方法为什么要改模块面向对象方法为什么只改局部——那你的分数一定低不了。5. 复习实操框架把笔记变成答题能力5.1 三轮复习法建议——别指望一遍背完第七章的内容说多不多说少不少如果离考试还有一段时间我建议你按三轮来复习每轮重点不一样。第一轮是通读笔记把概念串成地图。你可以拿一张A4纸从“面向对象设计”这个中心词出发往外画分支设计目标、设计原则、设计步骤、设计工具、常见关系。这一轮不要求背下来只要求你合上书能大概说出这一章讲了哪几块每一块是什么主题。做到这个程度看到题目就不会完全懵。第二轮是做题重点练设计题。找课程配套练习册或者往年真题至少做三到五道“根据需求画类图”的题目。这里有个关键每做完一道题都要对照答案总结“我漏掉了哪些点”。是漏写了关联关系还是把方法可见性写错了还是没写控制类把这些漏洞记下来比盲目做十道题更有效。第三轮是用项目反推加深理解。手头如果有课程设计的代码挑一个小模块看看它是不是符合面向对象设计的原则如果不符合试着在不改功能的前提下重构一下。这个过程不需要完整重构只要做一场“纸上演练”就够了。很多同学就是通过这一步突然理解了接口、组合、依赖注入到底有什么用。5.2 高频考点清单与一句话记忆这里整理一张“考前速记清单”每一条都配上好记的口诀或类比方便你在考前一天快速过一遍对象 vs 类对象是“具体实体”类是“创建实体的模板”。类比类是模具对象是模具压出来的工件。封装属性私有、方法公有对外只暴露接口。记忆点“管好自家事少让别人碰数据。”继承子类复用父类但别为了复用而乱继承。记忆点“猫是动物但动物不一定是猫。”多态同一消息发给不同对象产生不同行为。记忆点“同一个‘叫’方法猫叫喵狗叫汪。”内聚/耦合高内聚、低耦合。记忆点“一个类做好一件事类之间少拉拉扯扯。”聚合 vs 组合整体解散后部分是否存活。记忆点“班级没了学生还在聚合订单没了订单项也没了组合。”开闭原则不改旧代码也能加新功能。记忆点“扩展可以修改免谈。”里氏替换原则子类要能替换父类而不出错。记忆点“凡是能用动物的方法换猫也正常。”依赖倒置原则面向接口编程不要面向具体实现。记忆点“高层不碰底层细节大家都对着接口说话。”这张表不需要全背但至少考前要能快速说出每条的中心思想。考试里很多选择题就是变着法考这些概念你记住一句话往往就能排除两个错误选项。6. 典型例题复盘图书馆管理系统设计全过程6.1 从需求到类图一步步走一遍为了让你把前面几节串起来这里我们用“图书馆管理系统”的简化需求做一次完整复盘。假设需求描述是管理员可以新增图书、删除图书读者可以查询图书、借阅图书、归还图书借阅前要检查读者是否有逾期未还的图书如果有则不允许再借借阅记录要保存借出日期和归还日期。拿到这道题先别急着画框框。按我刚才说的方法先列实体类图书、读者、借阅记录。再列边界类管理员界面、读者查询界面。最后列控制类借书控制器、还书控制器。有些人可能会问管理员新增图书算不算控制逻辑算但如果你把每个操作都做一个控制器类就会爆炸。实际设计中可以把相关操作合并到一个管理控制器里比如图书管理控制器负责新增和删除图书。接下来定属性和方法。图书类属性至少包括图书编号、书名、作者、是否在馆读者类属性至少包括读者编号、姓名、可借数量;借阅记录类属性至少包括记录编号、图书编号、读者编号、借出日期、归还日期。方法层面图书类可以设计成“借出()”“归还()”“查询状态()”读者类可以设计成“查询借阅记录()”借阅记录类可以设计成“创建记录()”“更新归还日期()”。注意借书这个业务不能只靠图书类自己完成它需要控制器去协调图书、读者和借阅记录三个类所以借书控制器里应该有“校验借阅资格()”“执行借书()”这类方法。最终画类图时要注意三种关系读者和借阅记录是一对多关联图书和借阅记录是一对多关联读者和图书通过借阅记录形成间接的多对多关系。这里有一个高频考点在借书场景里借阅记录是当成一个独立的聚合根还是当成图书或读者的附属我建议把借阅记录设计成单独实体类不要塞到读者类里面去因为它的生命周期跟读者不完全一致而且后续扩展“续借”“预约”等功能都更方便。6.2 批改学生作业时最常见的六个错误第一把“管理员”设计成实体类。管理员的信息如果只是用来登录确实需要一个用户类来存储用户名和密码但管理员本身更像是系统使用者核心业务逻辑里的实体应该围绕图书、读者、借阅记录展开。如果你把“管理员”和“读者”并列成本质相同的实体会让理解出现偏差。第二忘记标注关联的多重性。很多同学画一条线连接两个类就完了读者和借阅记录之间到底是“1对多”还是“多对多”不写清楚。要知道多重性是类图设计题的核心得分点考试时一定要在关联线两端标上数字或字符1、0..*、1..*等。第三所有属性都写成public。这在课程作业里太常见了本质是把类当成一个普通结构体在用。考试时至少要把大部分属性设计成private只把真正需要被外部调用的方法设成public否则“封装”这个设计目标就体现不出来。第四不区分聚合和组合。比如图书和图书封面图书删除了封面信息还存着有意义吗没有所以应该用组合关系。而读者和借阅记录读者注销了借阅记录一般还需要保留备查所以用聚合或者普通关联更合适。细节决定设计质量。第五顺序图和类图对不上。顺序图里让“读者”对象直接修改“借阅记录”的归还日期但类图里借阅记录的归还日期属性却是private也没有提供对应方法这明显矛盾。设计文档内部要保持一致否则实现阶段根本没法写代码。第六方法只写名字不写参数和返回类型。比如“借出()”到底接收什么参数、返回什么完全没写。复习时我建议你养成习惯每个操作都写成完整签名即使不写完整类型也要写个名称因为考试阅卷时“方法设计是否完整”本身就是评分点。7. 最后复习时我想再提醒的几个经验坑这章复习到最后一两天很多同学会陷入“背了又忘”的焦躁状态这很正常。我个人的做法是不再看长篇大论只拿着自己整理的高频考点清单对着每一章的名称回想“这个知识点到底解决什么问题”。比如“聚合和组合”解决的是生命周期如何管理的问题“依赖倒置”解决的是需求变化时怎么隔离影响的问题。带着问题去回忆比盯着笔记反复读要有效得多。还有一个很实际的建议如果时间允许把第七章的所有例子都用手画一遍类图。手画和看画完全是两种体验手画更容易暴露你没搞懂的地方。我也是在复习考研那阵子才发现光看答案觉得自己会了一合上答案连“多重性”放哪头都能标反。这种低级错误只有在动笔时才会暴露。最后分享一个小技巧考前可以准备一道“万能母题”比如图书管理系统或在线购物系统把这一章的所有知识点都套进去。考试遇到陌生题目时先想想这道题的实体类是不是也分实体、边界、控制三类关系是不是也逃不开关联、聚合、组合消息协作是不是也可以用顺序图来梳理。你会发现题目再怎么变内核都是同一套面向对象的设计方法你只要把这套方法练熟了第七章基本就是送分题。
返回列表