
备考软件设计师这件事很多人栽在第七章不是因为它难而是因为它“看起来不难”。面向对象技术这章的概念什么封装继承多态但凡写过几行代码的人都能说上两句于是复习的时候一眼扫过去觉得都懂真到做题的时候发现四个选项排除掉两个剩下两个怎么看都对。我在带人复习和自测的过程中反复见到这个现象所以这一章我打算换个讲法不按教材顺序念概念而是按“考试到底怎么考、坑埋在哪里、怎么把概念变成能得分的判断力”来拆。这一章在软件设计师考试里的分量不轻上午题稳定出分下午题的设计部分也常常要你用面向对象的思路去补全类图或者判断关系。它不像数据结构那样需要大量计算也不像操作系统那样概念庞杂它的特点是概念密度高、概念之间容易混淆、题目表述稍微换个词答案就变。所以复习策略不能是“背定义”而应该是“建立一套能快速分辨的判断框架”。下面我按自己实际复习时用的思路把这一章拆成几块来讲每一块都告诉你为什么这么理解、考试会怎么绕你、以及我踩过的那些坑。1. 先搞清楚这章到底在考什么能力很多人复习面向对象技术上来就背“封装是隐藏内部实现”背完做题还是错。问题出在没有先建立对考试意图的判断。软件设计师考这一章本质上不是考你会不会背概念而是考三件事你能不能从一段描述里识别出它说的是哪个概念、你能不能判断两个概念之间的关系、你能不能把 UML 图里的结构和代码结构对应起来。这三件事对应三种题型复习的时候要分开练。1.1 概念识别题的出题套路概念识别题是上午题里最常见的形式给你一句话描述某个特性问这体现了面向对象的哪个特征。这类题看着简单但它特别喜欢在“封装”和“信息隐藏”、“继承”和“泛化”、“多态”和“重载”这几对之间做文章。我印象很深的一道题大意是“对象的内部状态对外不可见只能通过公开的操作访问”选项里封装和信息隐藏都出现了。标准答案给的是封装因为封装强调的是“把数据和操作捆绑在一起并控制访问”而信息隐藏更偏向“隐藏实现细节”这个结果。这两个概念在教材里经常一起出现但考试要你区分的时候你得知道封装是手段、信息隐藏是目的之一。我的做法是把这类容易混的概念做成一张对照表左边写概念、中间写它最核心的一句话、右边写它最容易和谁混。复习到后期我看到题干里的关键词就能条件反射地对应到概念上。比如看到“同一消息不同响应”就是多态看到“子类复用父类”就是继承看到“对外只暴露接口”就往封装上靠。这个反射不是靠背是靠反复做真题训练出来的我建议至少把近五年的真题里这一章的题目全部过一遍把每道题考的是哪个概念标出来你会发现考来考去就那几个点。1.2 关系判断题为什么最容易丢分关系判断题比概念识别难一个档次因为它要求你理解概念之间的层次和依赖。典型的是给你几个类问它们之间是关联、聚合、组合还是依赖。这四个关系在 UML 里都有对应的图形表示但考试经常不给图只给文字描述让你判断。我见过一道题描述“一个部门包含多个员工部门撤销后员工仍然存在”这就是典型的聚合因为整体和部分的生命周期不同步。如果描述改成“一个订单包含多个订单项订单删除后订单项也删除”那就是组合生命周期同步。这里的关键判断依据是生命周期是否同步而不是“包含”这个词本身。很多人看到“包含”就选组合这是最大的坑。我的经验是判断聚合和组合先问一句“整体没了部分还能独立活吗”能独立活就是聚合不能就是组合。关联和依赖的区别则在于耦合强度依赖是临时的、方法级别的使用关联是长期的、属性级别的持有。这几个判断标准我在下面还会展开讲因为它是这一章下午题也常用的思路。1.3 图与代码对应题的复习方法这类题在下午题里出现得多给你一张类图让你补全代码或者判断代码里某个方法该放在哪个类里。它考的是你能不能把 UML 的结构翻译成程序结构。复习这类题我的建议是不要只看图一定要动手把图翻译成代码哪怕是最简单的伪代码。比如类图里 A 类有一个指向 B 类的箭头旁边标着 1 对多你就要能写出 A 类里有一个 B 类型的集合属性。这个动作做多了图和代码之间的映射就变成直觉了。我当初复习的时候专门拿了一个笔记本把真题里出现的类图都手抄一遍然后在旁边用 Java 或者 C 的语法把类结构写出来。这个笨办法花时间但效果很实在因为下午题考的就是这个转换能力。而且手抄的过程会强迫你注意图上的细节比如箭头方向、多重性标注、抽象类是不是斜体这些细节往往就是判分点。2. 封装、继承、多态三个必须掰开揉碎的核心这三个概念是面向对象的基石也是考试反复出题的地方。但我要说的不是它们的定义而是它们在考试里怎么被“变形”来考你以及复习时容易忽略的细节。2.1 封装的边界到底划在哪里封装的核心是“把数据和对数据的操作放在一起并控制外部访问”。考试里考封装常见的是给你一段代码问哪个地方违反了封装原则。比如一个类把成员变量声明成 public外部可以直接改这就违反了封装。或者一个类提供了 getter 和 setter但 setter 里没有任何校验考试也可能拿这个做文章问这样设计的问题在哪。我复习封装的时候总结了一个判断标准外部能不能在不经过类自身逻辑的情况下改变对象状态。如果能封装就是有漏洞的。这个标准比背定义好用因为它可以直接套到代码题上。另外要注意封装不等于所有成员都私有有些类需要暴露一些常量或者不可变对象这不违反封装因为外部改不了它的状态。考试如果出到这种细节往往是送分题但前提是你理解封装的本质是“控制状态变更的入口”而不是“私有化一切”。还有一个容易被忽略的点是封装和可见性修饰符的关系。教材里讲封装经常提到 private、protected、public但考试不会只考修饰符的含义它会考你“这个成员应该用什么修饰符”这种设计判断。比如父类里一个方法只希望子类能访问、外部不能访问那就该用 protected。这种题考的是你对封装边界的实际把控复习的时候要把修饰符和“谁能访问”对应清楚别只记符号。2.2 继承的“is-a”判断为什么经常失效继承的经典判断是“is-a”关系子类必须是父类的一种。这个原则没错但考试里经常出现一些描述用 is-a 一套好像也对但标准答案偏偏不是继承。比如“学生和研究生”研究生是学生的一种用继承没问题。但如果描述是“鸟和翅膀”翅膀不是鸟的一种那就不能用继承应该是组合或者聚合。问题在于有些描述模棱两可比如“管理员和用户”管理员是用户的一种继承合理但如果系统里管理员和用户的权限体系完全独立硬用继承反而不好。我的经验是考试里判断该不该用继承除了 is-a还要看子类是不是真的能替换父类而不改变程序语义这就是里氏替换原则的通俗版。如果子类继承过来之后某些父类方法在子类里必须抛异常或者空实现那这个继承就是有问题的。考试有时候会给你一个继承结构问你哪里违反了里氏替换这种题就是考这个判断。复习的时候把里氏替换原则理解成“子类能不能顶替父类干活而不出岔子”比背定义管用。另外继承在考试里还经常和“复用”绑在一起考。要注意继承是实现复用的一种手段但不是唯一手段组合也能复用而且更灵活。考试如果问“以下哪种方式更好地实现了复用”有时候答案会是组合而不是继承因为组合的耦合度更低。这个点在下午题的设计部分也会出现看到“优先使用组合而非继承”这种表述要能理解它背后的理由是降低耦合、避免继承层次过深。2.3 多态的两种形式在题里怎么区分多态是这一章最容易出难题的地方因为它分编译时多态和运行时多态。编译时多态靠的是重载运行时多态靠的是重写加父类引用指向子类对象。考试经常把这两个混在一起考给你一段代码问输出什么或者问哪个是多态的表现。我区分这两个的办法很简单看方法在什么时候确定。重载的方法在编译阶段就根据参数列表确定了所以叫静态多态重写的方法要等到运行时根据对象的实际类型来确定所以叫动态多态。考试里如果代码是Parent p new Child(); p.method();这种形式考的就是动态多态method 的执行看的是 Child 的实现。如果代码是同一个类里同名不同参的方法调用考的就是重载。这里有个坑我踩过有些题会故意在子类里写一个和父类“看起来一样但参数类型不同”的方法问这是重写还是重载。答案是重载因为参数列表不同它没有覆盖父类的方法。判断重写要看方法签名是否完全一致包括方法名、参数类型和个数返回类型要兼容。这个细节在选择题里经常出现复习的时候要专门练几道这种“形似重写实为重载”的题。多态还有一个常考点是“向上转型”和“向下转型”。向上转型是子类对象赋给父类引用安全自动进行向下转型是父类引用转回子类需要显式转换而且可能失败。考试里会问哪种转型需要类型检查答案就是向下转型。这个点不难但容易和基本类型的转换混复习时注意区分对象转型和数值转换是两套规则。3. UML 关系判断聚合、组合、关联、依赖的实战分辨法UML 关系判断是这一章下午题的高频考点也是上午题喜欢出文字描述题的地方。这四个关系如果只背定义做题时还是会犹豫因为它们之间的界限在文字描述里经常模糊。我在下面给一套我自己用的分辨流程按这个流程走大部分题能快速定下来。3.1 从生命周期同步性切分聚合与组合聚合和组合都是整体与部分的关系区别就在生命周期。组合是强关系整体销毁部分也跟着销毁聚合是弱关系整体销毁部分可以独立存在。考试描述里如果出现“包含”“拥有”这类词先别急着选往下看有没有关于销毁的暗示。我整理了一个判断表做题时对照着看描述特征判断结果理由部分随整体创建而创建、销毁而销毁组合生命周期同步部分可以独立于整体存在聚合生命周期不同步整体负责部分的创建和释放组合控制权在整体部分可以从外部传入整体聚合部分来源独立这个表的关键是最后一行组合里的部分通常由整体自己 new 出来聚合里的部分往往是通过构造函数或者 setter 从外部传进来的。考试如果给代码让你判断看部分对象的创建位置就能定。这个细节教材里不一定强调但做题时非常管用。3.2 关联与依赖看耦合的“持久性”关联和依赖的区别在于关系的持续时间。关联是结构性的、长期的通常体现为类里有一个属性持有对方依赖是临时的、使用性的通常体现为方法参数、局部变量或者返回值。考试描述里如果出现“调用”“使用”“传入”这类词大概率是依赖如果出现“拥有”“成员”“长期持有”大概率是关联。我判断的时候会问一句这个关系是不是体现在类的属性里。如果是关联如果只是某个方法里用了一下依赖。这个判断标准在代码题里特别好用因为代码里属性声明和方法参数的区别一目了然。另外要注意关联还有单向和双向、一对一和一对多之分考试如果给多重性标注要能对应到代码里的单个引用还是集合引用。依赖还有一个容易忽略的点依赖是单向的A 依赖 B 不代表 B 依赖 A。考试有时候会给一个双向的使用关系问这是不是依赖答案可能是不是因为双向的长期使用更接近关联。这个边界比较细复习的时候把真题里涉及依赖的题都挑出来看一遍就能摸清出题人的尺度。3.3 泛化、实现与细化在题面里的信号词除了上面四个泛化和实现也是常考的。泛化就是继承关系子类到父类实现是类对接口的实现。这两个在 UML 里图形不一样泛化是实线空心三角实现是虚线空心三角。考试如果给图看线型就能分如果给文字泛化通常描述为“是一种”“是……的子类”实现通常描述为“实现了……接口”“遵循……协议”。这里有个信号词容易混“扩展”。扩展有时候指继承有时候指对原有功能的增强。考试里如果出现“类 B 扩展了类 A”一般指继承如果出现“通过扩展点增加功能”可能是在说某种设计模式或者插件机制。看到“扩展”这个词要结合上下文判断不能一概而论。4. 设计原则与设计模式从“知道”到“会选”软件设计师考试在这一章还会涉及设计原则和常见设计模式。设计原则考的是判断某个设计违反了哪条原则设计模式考的是识别模式或者选择模式。这两块内容多但考试深度有限关键是建立“看到问题能对应到原则或模式”的映射。4.1 单一职责和开闭原则的判断题眼单一职责原则说的是一个类只负责一件事。考试里判断是否违反单一职责看类里是不是混了多个不同方向的职责。比如一个类既负责计算又负责持久化那就违反了单一职责。这个判断不难难的是有些题描述得很隐蔽比如“类 A 负责解析配置并缓存解析结果”解析和缓存是两个职责虽然相关但可以分开这种题就可能考单一职责。开闭原则说的是对扩展开放、对修改关闭。考试里常见的考法是给你一个场景问哪种设计符合开闭原则。符合开闭原则的设计通常是抽象出一个稳定的接口新增功能通过新增实现类来完成而不是改原有代码。看到“新增类型时不需要修改原有代码”这种描述就是开闭原则的信号。我复习这两个原则的时候会把它们和具体的坏味道对应起来。单一职责对应“类太大、方法太多、变更原因多”开闭原则对应“每加一个功能就要改一堆 if-else”。这种对应关系建立起来之后判断题就不容易错。4.2 里氏替换和依赖倒置在代码题里的表现里氏替换原则前面提过核心是子类能替换父类。考试里如果给一个继承结构子类重写的方法抛出了父类没有声明的异常或者子类把父类的方法改成了空实现这就违反了里氏替换。判断的时候看子类有没有“削弱”父类的契约。依赖倒置原则说的是高层模块不应该依赖低层模块两者都应该依赖抽象。考试里常见的表现是如果代码里高层类直接 new 了低层类的具体对象就违反了依赖倒置如果高层类持有的是低层类的接口引用就符合。这个原则和“面向接口编程”是一回事看到“依赖抽象而非具体”的表述就往这上面靠。这两个原则在下午题的设计部分经常一起出现因为它们是做好设计的基础。我复习的时候会把真题里涉及这两个原则的代码片段单独摘出来分析它哪里违反了、怎么改符合这个分析过程比背原则名称有价值得多。4.3 常见模式识别从场景描述反推模式设计模式这一章考试不会考太深主要是识别。常考的模式有单例、工厂、观察者、策略、适配器这几个。识别的方法是抓场景描述里的关键词。单例的关键词是“唯一实例”“全局访问”工厂的关键词是“创建对象但不暴露创建逻辑”观察者的关键词是“一对多依赖”“状态变化通知”策略的关键词是“算法可替换”适配器的关键词是“转换接口”“兼容已有类”。我建议把每个模式的“意图一句话”和“关键词”做成卡片复习的时候快速过。考试里如果描述里出现了某个模式的关键词基本就能定。但要注意有些题会故意用两个模式都沾边的描述这时候要看主要意图。比如“根据条件选择不同的算法”策略和工厂都可能沾边但策略强调的是算法可替换、运行时切换工厂强调的是创建。看描述的重点落在“用”还是“造”上就能分开。5. 我踩过的坑和复习节奏建议这一章我复习的时候走过弯路也总结出一些节奏上的经验。先说坑再说节奏。5.1 三个最容易反复错的点第一个坑是把“重载”当成“多态”的唯一形式。多态包括重载和重写但考试里如果问“多态的实现机制”答案往往要覆盖两者只答重写会丢分。我当初做一道简答题就吃了这个亏只写了动态多态漏了静态多态。第二个坑是聚合和组合的判断只看“包含”二字。前面说过要看生命周期。我有一道题就是因为看到“包含”直接选了组合结果描述里有一句“部分可以独立存在”答案应该是聚合。这个坑的教训是做题要把描述读完关键词往往在最后一句。第三个坑是设计原则的判断题把单一职责和接口隔离搞混。单一职责是一个类一个职责接口隔离是一个接口不要太大、不要强迫实现类实现用不到的方法。这两个原则角度不同一个针对类一个针对接口。考试里如果描述的是“接口里方法太多实现类被迫实现空方法”那是接口隔离如果描述的是“类承担了多个职责”那是单一职责。5.2 复习节奏怎么安排更稳我的建议是把这一章分三轮。第一轮通读教材把概念过一遍不求记住只求知道有哪些概念。第二轮做真题按题型分类做概念识别题一组、关系判断题一组、代码对应题一组每组做完把错题的原因写下来。第三轮是考前一周只看错题和对照表不再看新内容。时间分配上第一轮两到三天第二轮四到五天第三轮一到两天。这个节奏是根据这一章的分值和我自己的遗忘曲线定的不一定适合所有人但思路可以参考先铺面、再收点、最后只盯薄弱点。面向对象技术这一章的特点是概念之间关联强铺面的时候不要孤立地背要把封装、继承、多态串起来理解把 UML 关系和代码结构对应起来这样第二轮做题的时候才能快速反应。还有一个实操上的小技巧做关系判断题的时候在题干旁边把判断依据写下来比如“生命周期不同步→聚合”这样即使做错了回头看也能知道是依据错了还是概念错了。这个习惯帮我省了很多复盘时间因为错题原因一目了然。最后说一句我自己的体会这一章不怕你基础差就怕你觉得自己都懂然后跳过练习。概念看着简单但考试考的是判断力判断力只能靠做题和复盘练出来。把真题里这一章的题做透比看三遍教材管用。