软件设计师下午第三题:面向对象与设计模式识别全解析
发布时间:2026/11/3 17:49:47 锦皓数字建站

1. 这道题到底在考什么从“下午题第三道”说起软件设计师下午卷的第三道题历来是整张卷子里最“接地气”的一道。上午题考的是广度从数据结构到操作系统再到标准化知识什么都可能冒出来下午题前两道考的是数据流图和数据库设计偏建模而第三道几乎年年落在面向对象与程序设计这一块语言载体通常是 Java 或 C2018 上半年这一份也不例外落在 Java 上。很多人第一次接触这道题时的反应是“代码好像看得懂但不知道从哪下手”。这很正常。这道题给出一段不长的 Java 代码配上若干空位要求你补全代码、指出设计模式、说明类与类之间的关系。它不像算法题那样要求你从零推导也不像数据库题那样要求你画 E-R 图它考的是你能不能读懂一段别人写好的、结构完整的面向对象代码并且理解它为什么这么设计。我把这类题的核心能力拆成三层第一层是语法层你得看得懂继承、接口、抽象类、泛型、集合这些基本写法第二层是设计层你得能识别出这里用了什么设计模式为什么用这个模式而不是别的第三层是表达层你得能用规范的语言把类之间的关系、方法的职责说清楚。三层里任何一层卡住这道题就丢分。这篇文章不打算只给你一份“参考答案”就完事。参考答案网上到处都是但很多人对着答案看一遍下次遇到同类题还是不会。我想做的是把这道题背后的东西拆开它为什么这么出、每个空位的判断依据是什么、设计模式怎么快速识别、以及我在实际做题和带人做题过程中总结出来的那些“看答案看不出来”的经验。适合正在备考软件设计师的读者也适合那些 Java 基础还行、但一遇到“设计模式识别”就发怵的开发者。需要说明的是由于原始题目正文没有提供下面涉及的具体代码结构和空位设置是我基于这类题目的常见命题规律做的合理还原与演绎重点在于方法和思路而不是逐字复现某一份真题。你完全可以把这里讲的分析框架套到任何一份同类题目上。2. 命题人的出题逻辑为什么偏偏是这段代码2.1 下午第三题的稳定套路把近些年的下午第三题摆在一起看会发现一个很稳定的结构给一个贴近实际业务的小场景比如订单处理、图形绘制、设备管理、报表导出然后用一段 Java 代码把这个场景的骨架搭出来中间挖四到五个空。空位的位置也很有讲究通常分布在这么几个地方抽象类或接口的定义处、具体子类的实现处、工厂或上下文类的创建逻辑处、以及客户端调用处。为什么这么分布因为这几个位置恰好对应面向对象设计的几个关键决策点。抽象类定义处考的是你对“什么是稳定的、什么是变化的”的判断子类实现处考的是多态落地工厂或上下文处考的是对象创建与使用的分离客户端调用处考的是你懂不懂“面向接口编程”。命题人不是随便挖空的每一个空都对应一个知识点这是这类题最值得琢磨的地方。我见过不少考生做题时习惯从第一个空顺着往下填填不出来就卡在那里。这个策略其实不太好。更有效的做法是先通读整段代码把场景、类名、方法名、注释都看一遍在脑子里建立起“谁调用谁、谁继承谁”的图景然后再回头逐个攻空。因为空与空之间往往有依赖关系后面的空可能反过来提示前面的空该怎么填。2.2 场景选择背后的考察意图2018 上半年这道题所属的批次场景设计上偏向“多类型对象的统一处理”。这类场景的好处是天然适合用多态来表达有一组相似但不完全相同的对象它们有共同的行为契约但各自实现不同。命题人把共同行为抽到抽象类或接口里把差异留给子类这个结构一旦立起来设计模式的影子就出来了。这里有个容易被忽略的点场景里的名词往往就是类名的来源动词往往就是方法名的来源。读题时把场景描述里的关键名词圈出来基本就能对上代码里的类把关键动作圈出来基本就能对上方法。这个对应关系不是巧合是命题人为了让题目“可读”而刻意保持的一致性。抓住这一点读代码的速度会快很多。2.3 空位设置的四种典型类型我把这类题的空位归纳成四种类型做题时先给每个空归个类心里就有底了空位类型常见位置判断依据典型填法声明型类/接口/抽象方法的定义处看子类有没有统一实现、看客户端怎么调用abstract、interface、方法签名实现型具体子类的方法体看方法名语义、看父类契约具体业务逻辑代码创建型工厂方法或上下文类内部看返回类型、看分支条件new具体子类、返回对象调用型客户端或上下文的使用处看多态调用意图通过父类型引用调用方法归好类之后你会发现声明型和调用型往往互相印证客户端如果用父类型引用去调某个方法那父类型里就一定有这个方法声明反过来父类型里声明了抽象方法子类就必须实现。这种互相印证是检查答案对不对的好办法。3. 逐空拆解每个空位的判断链路3.1 先建立类图心智模型再动笔动笔填第一个空之前我强烈建议先在草稿纸上画一个极简的类关系草图。不用画得多规范就是把类名写下来用箭头标出继承和组合关系把每个类里出现的方法名列在旁边。这个过程花不了两分钟但能帮你避开大量“填了后面发现前面错了”的返工。画草图的时候重点关注三件事第一有没有一个类是“根”其他类都从它派生第二有没有一个类是“管理者”它内部持有其他类的引用第三客户端代码里出现的变量类型是父类型还是子类型。这三件事基本决定了整段代码的设计骨架骨架清楚了空位填起来就是顺水推舟。我个人的习惯是用不同符号区分实线箭头表示继承菱形表示持有引用方法名直接写在类名下面。画完之后再对照代码看一遍确认没有遗漏的类。有时候题目会故意给一个“看起来像子类但其实没被用到”的类这种类往往是干扰项画图时标出来填代码时就不用管它。3.2 抽象方法声明空从子类反推父类契约声明型的空最考验“反向推理”能力。题目往往把父类里某个方法的修饰符或方法体挖掉让你填abstract或者补全方法签名。这时候不要盯着父类看要去看子类。具体做法是找到所有继承这个父类的子类看它们各自实现了哪些同名方法。如果多个子类都有同一个签名的方法且方法体各不相同那父类里这个方法大概率就是抽象方法。反过来如果某个方法在父类里有默认实现、子类只是选择性覆盖那它就不是抽象的。这个判断逻辑很朴素但非常可靠。还有一个细节值得注意抽象方法的声明里不能有方法体结尾是分号而普通方法即使方法体为空也得有花括号。有些空位就是考你这个格式细节填abstract void doSomething();是对的填abstract void doSomething(){}就错了。这种分丢得冤枉但确实常见。3.3 子类实现空方法名就是需求说明书实现型的空看起来最难其实最好拿分因为方法名已经把需求写明白了。比如方法叫calculateTotal那方法体里必然是累加逻辑方法叫formatOutput那必然是字符串拼接或格式化。命题人起方法名不会故弄玄虚基本都是“见名知意”。填这类空的时候我的经验是先看这个子类特有的属性有哪些。子类里定义的成员变量往往就是方法体里要用到的数据。比如子类里有个unitPrice和quantity那calculateTotal里大概率就是这两个相乘。再看父类或其他子类有没有可参考的实现同类题里经常有“一个子类给了完整实现另一个子类挖空”的情况照着已给的那个改一改逻辑就出来了。需要提醒的是子类实现空有时候会涉及对父类方法的调用也就是super.xxx()。如果子类的逻辑是在父类基础上做增量那这个调用就跑不掉。判断依据是看子类方法是不是“在通用逻辑上加点东西”如果是先调super再补自己的部分。3.4 工厂与上下文空对象创建为什么不能散落各处创建型的空通常出现在一个“工厂”或“管理器”类里方法根据某个参数返回不同的子类实例。这类空的核心是分支逻辑加对象实例化填起来不难但背后的设计意图值得说透。为什么命题人喜欢在这里挖空因为这里是“对象创建与对象使用分离”的体现点。如果客户端直接new具体子类那客户端就依赖了具体实现一旦新增子类客户端也得改。而把创建逻辑收拢到工厂里客户端只依赖父类型新增子类时只改工厂客户端不动。这就是开闭原则的落地。命题人挖这个空本质是在考你懂不懂“为什么要多一层工厂”。填的时候注意两点一是分支条件要和参数语义对应参数是字符串就按字符串匹配是枚举就按枚举分支二是返回值类型要是父类型不能是具体子类型否则多态就白搭了。这两点看着简单但每年都有人在这里栽跟头。3.5 客户端调用空面向接口编程的最后一公里调用型的空一般在main方法或某个客户端类里考的是“拿到父类型引用之后怎么用”。正确写法永远是通过父类型变量调用方法而不是强转成子类型再调。这个空填对了说明你真正理解了多态填错了说明还停留在“知道有继承但不会用”的阶段。举个典型场景工厂返回一个父类型引用客户端要调用它的业务方法。正确写法是Product p factory.create(type); p.doSomething();而不是Product p factory.create(type); ((ConcreteProduct)p).doSomething();。后者虽然也能跑但把多态的优势全丢了是典型的“能运行但设计错误”。命题人挖这个空就是想看你会不会犯这个错。4. 设计模式识别从代码结构反推模式名4.1 模式识别的三个抓手设计模式识别是这道题的高频考点也是很多人最没底的部分。其实识别模式不需要背下所有模式的完整定义抓住三个抓手就够了看对象怎么创建、看对象怎么组合、看行为怎么分配。对象创建集中在一个方法里、根据参数返回不同子类这是工厂模式家族的特征对象组合上出现“整体持有部分、部分可以独立变化”往组合模式或装饰器模式想行为分配上出现“把算法封装成独立类、上下文持有算法引用”那是策略模式。这三个抓手覆盖了下午题里出现频率最高的几类模式。我整理了一个快速对照表做题时对着扫一眼命中率很高代码特征高度疑似模式关键佐证一个方法根据参数 new 不同子类并返回父类型简单工厂 / 工厂方法有分支、返回父类型类内部持有同类型引用并可层层嵌套装饰器 / 组合构造器接收同类型参数上下文类持有算法接口引用可替换策略有 set 方法或构造注入全局唯一实例、私有构造器单例静态方法返回自身一系列算法各自成类、客户端选择策略 / 命令有统一接口、可互换需要强调的是模式识别不是猜谜答案必须能从代码里找到证据。你说这是策略模式就得指出哪个是策略接口、哪个是上下文、哪里发生了替换。光写个模式名不给理由阅卷时是拿不到完整分的。4.2 工厂家族与策略模式的易混点工厂和策略是下午题里最容易混的一对因为两者都涉及“一个父类型、多个子类型、一个选择逻辑”。区别在于意图工厂解决的是“对象怎么来”策略解决的是“行为怎么选”。判断方法很直接看那个持有父类型引用的类它是“创建”了子类对象还是“接收”了子类对象。如果是自己new出来的偏工厂如果是通过构造器或 setter 传进来的偏策略。再进一步工厂方法通常返回对象给外部用策略模式通常是上下文内部用这个对象干活。抓住“创建”和“使用”这两个动词基本不会混。还有一种情况是两者结合工厂负责造策略对象上下文负责用。这种复合结构在真题里出现过识别时分别指出两段逻辑即可不用纠结“到底算哪个模式”把两处意图都说清楚才是得分点。4.3 模式名写对了但理由写错照样丢分这是我在带人做题时反复强调的一点模式识别题的评分是按“模式名 判断依据”两部分给的。模式名蒙对了但依据写偏了分数会打折。比如你写“这是工厂模式因为有一个 create 方法”这个理由太弱create 方法谁都能叫。有力的理由应该是“因为该方法根据类型参数实例化不同的具体子类并将它们统一以父类型返回使客户端不依赖具体类”。写理由的时候尽量用“因为……使得……”的句式把设计意图带出来。阅卷人看的是你懂不懂这个模式解决了什么问题而不是你能不能背出模式的定义。这一点在备考时就要有意识地练别等到考场上现编。5. 类关系与多态那些答案里不会写的细节5.1 继承、实现、组合的表述规范下午题里经常要求用文字描述类之间的关系比如“类 A 与类 B 是什么关系”。这类题不难但表述不规范会丢分。规范的说法是继承叫“is-a”关系写成“A 继承自 B”或“A 是 B 的子类”实现叫“A 实现接口 B”组合叫“A 包含 B”或“A 持有 B 的引用”强调生命周期一致时可以说“A 由 B 组成”。很多人习惯笼统地说“A 和 B 有关系”这种表述在阅卷时等于没说。还有人不区分继承和实现把implements也说成继承严格来说是不准确的。Java 里类与类之间是单继承类与接口之间是实现接口与接口之间才是多继承这个区分在描述关系时必须清楚。5.2 多态调用在运行时的真实走向理解多态不能停留在“父类引用指向子类对象”这句话上得知道运行时到底发生了什么。当你写Animal a new Dog(); a.speak();时编译期检查的是Animal里有没有speak方法运行期执行的是Dog里覆盖后的speak。这个“编译看左边、运行看右边”的规则是判断调用型空位对错的根本依据。有个常见的坑如果子类没有覆盖父类方法那运行时执行的就是父类的版本如果父类方法是private或static的那根本谈不上多态调用的永远是父类版本。命题人有时候会在这里设陷阱给一个static方法让你判断输出很多人想当然地按多态去推结果就错了。看到static和private修饰的方法脑子里要立刻拉响警报。5.3 抽象类与接口的取舍判断题目里有时会问“这里为什么用抽象类而不是接口”或者反过来。回答这类问题要抓住两者的本质差异抽象类可以包含状态成员变量和部分实现接口在传统意义上只定义行为契约。如果这组类有共享的状态或通用的实现逻辑用抽象类更合适如果只是约定一组行为、各类实现完全独立用接口更合适。Java 8 之后接口可以有默认方法这个界限变得模糊了一些但下午题的代码风格通常还是偏传统的看到“有成员变量、有部分方法体”基本就是抽象类看到“全是方法签名、没有状态”基本就是接口。判断时以代码实际结构为准别被理论上的可能性绕进去。6. 考场上的时间分配与检查策略6.1 这道题该给多少分钟下午卷总共两个半小时四道题有些年份是五道选四。第三道题的分值通常是 15 分按分值比例给它 25 到 30 分钟比较合理。但实际做题时我建议把它放在第二或第三顺位做不要一上来就啃它也不要留到最后。原因是这道题需要“进入状态”你得先把整段代码读顺建立类图心智模型这个过程需要一点沉浸感。如果放在最后、时间紧张读代码会变得急躁容易漏看关键信息。放在中间做前面已经做完了数据流图脑子热起来了后面还有时间检查节奏最舒服。6.2 填完之后怎么自查填完所有空之后别急着翻页花三分钟做一次“通读验证”把填好的代码从头到尾读一遍重点看三件事。第一语法通不通抽象方法有没有方法体、子类有没有实现所有抽象方法、返回值类型对不对。第二逻辑通不通工厂的分支有没有覆盖所有子类、客户端调用的方法在父类型里存不存在。第三设计意图通不通你填的模式名和代码结构对不对得上。这三遍读下来大部分低级错误都能揪出来。我见过太多人因为少写一个abstract或者返回值类型写错而丢分这些错误通读一遍就能发现可惜考场上很多人填完就走。6.3 遇到完全没思路的空怎么办总会有那么一个空怎么看都没头绪。这时候的策略是“先跳过、后回补”别在一个空上耗超过三分钟。跳过去之后先做后面的空后面的空往往能反过来提示前面的空该怎么填。比如工厂方法里的分支条件填不出来先看客户端传的参数是什么参数类型和取值往往就暗示了分支该怎么写。如果到最后还是没思路那就按“最合理的猜测”填一个别空着。这类题按空给分空着必然零分填了至少有蒙对的概率。填的时候优先填语法上说得通的哪怕业务逻辑不确定语法正确也能拿到部分分。7. 从这道题延伸出去真正该练的能力做完这道题如果只是对完答案就放下那它的价值就浪费了一大半。这道题真正想训练的能力是“读懂一段陌生代码并判断其设计合理性”这个能力在实际开发里比任何单个知识点都值钱。我的建议是做完真题之后自己动手把这段代码敲进 IDE 里跑一遍把空位故意填错几个看看编译报什么错、运行出什么结果。这种“故意犯错”的练习比看十遍答案都管用因为错误信息会逼着你去理解每个修饰符、每个类型声明的真实作用。跑通之后再试着给这段代码加一个新子类看看需要改哪些地方。如果只需要改工厂里加一个分支、其他地方不动说明你理解了开闭原则如果到处都要改说明这段代码的设计还有改进空间这本身就是很好的思考素材。另外设计模式的识别能力不是靠背定义背出来的是靠“见过足够多的真实结构”练出来的。平时看开源代码或者公司项目时有意识地去找“这里是不是用了工厂”“这里是不是策略”久而久之考场上扫一眼代码结构模式名自己就冒出来了。这种直觉才是这道题真正想筛选的东西。最后分享一个我自己做题时的小习惯每道题做完在草稿纸角落写一句话总结“这道题考的核心是什么”。2018 这道题我写的是“多态落地与创建分离”。一句话概括既是对这道题的收束也是对自己知识体系的一次归档。下次遇到同类题脑子里调出来的就是这句话而不是一堆零散的知识点。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。