简介本资源是一套基于Java实现的《动物运动会》综合教学系统面向软件工程专业学生、设计模式初学者及Java进阶开发者旨在通过真实业务场景深度理解并实践31种经典设计模式。系统以动物参赛、大厅交互、赛事执行等完整流程为载体融合抽象工厂模式构建动物族系、适配器模式桥接异构设备接口、享元模式优化动物对象内存开销等关键架构实践覆盖UML类图、设计文档与可运行代码全链路。压缩包共330个文件含162个核心Java源码如Athlete、Game、OneHundredMetersRunning等、163个编译后class文件辅以1份Word设计文档、1份LICENSE协议及IDE项目配置文件整体仅1.28MB结构精炼、即下即用。已有1862人学习下载读者可直接导入IDE运行全部功能模块获取完整设计思路推演、模式应用边界说明及典型类职责划分范例是少有的将31种模式有机整合于单一业务系统的高质量教学案例。1. 动物运动会系统为什么用设计模式写个“运动会”比写计算器还烧脑你手头有个大作业——动物运动会系统要求用上“多种设计模式”还要交文档。表面看就是几个动物跑跳投、计分排名、裁判发令的简单逻辑但真动手才发现一旦加了“狮子报名短跑却想兼项跳高”“裁判临时被替换为AI模块”“赛后要导出PDFExcel实时大屏三套结果”代码立刻变成一锅粥类之间牵一发而动全身改个计分规则得翻遍6个文件新增一个“水生项目组”得重写调度逻辑……这不是功能堆砌是典型的可扩展性失能现场。这个系统本质是一道“面向对象建模压力测试题”它不考你会不会写if-else而考你能不能在需求还没定死时就提前把变化点动物类型、比赛规则、裁判行为、输出格式全部隔离出来。适合正在学《软件设计》课程、被UML图和模式对比表折磨得睡不着觉的开发者也适合刚入职发现老系统“改一行崩三处”的新人——它用最直观的动物世界把抽象工厂、策略、观察者、命令、状态这些模式全塞进一个可运行、可调试、可画类图的真实上下文里。别把它当作业应付它是你第一次亲手给代码“搭脚手架”的实战场。2. 从零搭骨架用5个核心设计模式串起动物运动会主干动物运动会不是静态展示而是一套动态协作系统动物注册、项目分组、裁判指派、成绩录入、结果发布、赛后统计……每个环节都藏着变化。硬编码会迅速失控必须用设计模式提前划清边界。我们按实际开发顺序用5个最贴合场景的模式构建主干不堆概念只讲为什么选它、怎么落地、代码里哪行是关键。2.1 用抽象工厂模式隔离“动物家族”与“项目类型”的耦合运动会里动物不是孤立个体而是按生态位分组陆生狮子、猎豹、水生海豚、章鱼、两栖青蛙、蝾螈。每个组支持的项目不同水生不比跨栏陆生不比潜水裁判资质也不同。如果用if-else判断动物类型再new具体类新增“飞行动物组”就得改所有创建逻辑。抽象工厂正是为此而生——它把“创建什么动物”和“创建什么项目”这两件事打包成一个契约。from abc import ABC, abstractmethod class AnimalFactory(ABC): abstractmethod def create_runner(self) - Animal: pass abstractmethod def create_jumper(self) - Animal: pass abstractmethod def create_swimmer(self) - Animal: pass class TerrestrialFactory(AnimalFactory): def create_runner(self): return Cheetah() # 猎豹专精短跑 def create_jumper(self): return Kangaroo() # 袋鼠弹跳力强 def create_swimmer(self): return Otter() # 水獭勉强能游 class AquaticFactory(AnimalFactory): def create_runner(self): return Dolphin() # 海豚水中冲刺即“跑” def create_jumper(self): return Octopus() # 章鱼喷射推进算“跳” def create_swimmer(self): return Seal() # 海豹是游泳专家关键说明TerrestrialFactory和AquaticFactory是具体工厂它们决定了“陆生组动物能报哪些项目”。系统启动时只需注入一个工厂实例如factory TerrestrialFactory()后续所有动物创建都通过factory.create_runner()调用完全不知道背后是猎豹还是狮子。新增AerialFactory只需实现三个抽象方法其他代码0修改。这就是工厂模式的价值把“创建谁”的决策权从客户端代码里彻底摘出去。2.2 用策略模式让“计分规则”可插拔告别if-else地狱运动会计分千奇百怪短跑看时间越短越好跳高看高度越高越好游泳看圈数越多越好。如果写成if event_type sprint: score 100 - time加个“障碍赛”就得再加elif很快变成嵌套深渊。策略模式把每种计分逻辑封装成独立类运行时按需切换。from abc import ABC, abstractmethod class ScoringStrategy(ABC): abstractmethod def calculate_score(self, performance: float, *args) - int: pass class TimeBasedScoring(ScoringStrategy): # 短跑、游泳计时项目 def calculate_score(self, time_sec: float) - int: # 基础分100每快0.1秒加1分上限120 return min(120, int(100 (10.0 - time_sec) * 10)) class HeightBasedScoring(ScoringStrategy): # 跳高、撑杆跳 def calculate_score(self, height_m: float) - int: # 每0.1米加5分起跳基准1.5米 return max(0, int((height_m - 1.5) * 50)) # 运动会主控类持有策略引用 class Competition: def __init__(self, scoring_strategy: ScoringStrategy): self.scoring_strategy scoring_strategy # 运行时注入 def record_result(self, performance: float): score self.scoring_strategy.calculate_score(performance) print(f得分{score}) return score # 使用时动态切换 comp Competition(TimeBasedScoring()) # 短跑比赛 comp.record_result(9.58) # 输出得分120 comp Competition(HeightBasedScoring()) # 跳高比赛 comp.record_result(2.45) # 输出得分475参数说明ScoringStrategy是策略接口TimeBasedScoring和HeightBasedScoring是具体策略。Competition类不关心具体怎么算分只调用calculate_score()。新增“圈数计分”写个LapBasedScoring类传给Competition构造函数即可。策略模式的核心是把算法的“变”与使用的“不变”彻底分离。这里“变”的是计分公式“不变”的是“记录成绩→计算得分→存储结果”这一流程。2.3 用观察者模式实现“裁判-成绩-大屏”的松耦合联动运动会最怕信息不同步裁判按下计时器成绩系统没收到大屏还在显示上届数据。硬编码广播如score_system.update(); dashboard.refresh()会让裁判类依赖所有下游改一个显示模块就得动裁判代码。观察者模式让裁判只通知“我有新成绩了”谁爱听谁注册互不相识。from typing import List, Callable class Subject(ABC): def __init__(self): self._observers: List[Callable] [] def attach(self, observer: Callable): if observer not in self._observers: self._observers.append(observer) def notify(self, *args, **kwargs): for observer in self._observers: observer(*args, **kwargs) class Judge(Subject): def __init__(self): super().__init__() self._current_time 0.0 def press_stopwatch(self, time_sec: float): self._current_time time_sec self.notify(eventresult_recorded, athleteCheetah, timetime_sec) # 具体观察者成绩系统 def score_system_update(event, **kwargs): if event result_recorded: print(f[成绩系统] 收到 {kwargs[athlete]} 成绩{kwargs[time]}s) # 具体观察者大屏显示 def dashboard_refresh(event, **kwargs): if event result_recorded: print(f[大屏] 实时刷新{kwargs[athlete]} 冲线{kwargs[time]}s) # 绑定观察者 judge Judge() judge.attach(score_system_update) judge.attach(dashboard_refresh) # 裁判触发事件所有观察者自动响应 judge.press_stopwatch(9.58) # 输出 # [成绩系统] 收到 Cheetah 成绩9.58s # [大屏] 实时刷新Cheetah 冲线9.58s逻辑说明Judge是被观察的主体Subjectscore_system_update和dashboard_refresh是观察者Observer。attach()方法让观察者“订阅”裁判notify()方法广播事件。裁判类完全不知道观察者是谁、有几个、做什么——它只负责“发出通知”。这种解耦让系统具备极强的可扩展性要加个“短信通知家长”功能写个新函数sms_alert()judge.attach(sms_alert)一行搞定裁判代码0改动。2.4 用命令模式把“裁判操作”封装成可撤销、可队列、可日志的对象裁判的操作不是原子动作按计时器、举旗示意、录入成绩、提交审核……这些操作可能需要撤销误按了、重做网络失败后重发、排队执行避免并发冲突、记录审计日志。如果直接调用judge.record_time(9.58)这些横切关注点undo/redo/log会污染核心逻辑。命令模式把每个操作包装成对象自带执行、撤销、描述能力。from abc import ABC, abstractmethod from datetime import datetime class Command(ABC): abstractmethod def execute(self): pass abstractmethod def undo(self): pass property abstractmethod def description(self) - str: pass class RecordTimeCommand(Command): def __init__(self, judge: Judge, athlete: str, time_sec: float): self.judge judge self.athlete athlete self.time_sec time_sec self._original_score None # 用于undo def execute(self): # 执行前记录原始状态假设成绩可覆盖 self._original_score self.judge.get_score(self.athlete) self.judge.record_time(self.athlete, self.time_sec) print(f✅ 执行{self.description}) def undo(self): if self._original_score is not None: self.judge.set_score(self.athlete, self._original_score) print(f↩️ 撤销{self.description}) property def description(self) - str: return f为{self.athlete}录入成绩 {self.time_sec}s # 裁判类持有一个命令历史栈 class Judge: def __init__(self): self._command_history: List[Command] [] self._scores {} def record_time(self, athlete: str, time_sec: float): self._scores[athlete] time_sec def get_score(self, athlete: str) - float: return self._scores.get(athlete, 0.0) def set_score(self, athlete: str, score: float): self._scores[athlete] score def execute_command(self, command: Command): command.execute() self._command_history.append(command) def undo_last(self): if self._command_history: last self._command_history.pop() last.undo() # 使用示例 judge Judge() cmd RecordTimeCommand(judge, Cheetah, 9.58) judge.execute_command(cmd) # ✅ 执行为Cheetah录入成绩 9.58s judge.undo_last() # ↩️ 撤销为Cheetah录入成绩 9.58s参数说明RecordTimeCommand封装了“录入成绩”这一操作的所有上下文谁、什么成绩、原始值。execute()执行业务逻辑undo()恢复状态description提供可读日志。Judge类通过execute_command()统一调度不再直接调用业务方法。命令模式的价值在于它把“做什么”变成了“一个东西”这个东西可以存储、传输、回放、分析——这是实现事务、审计、工作流的基础。2.5 用状态模式管理“比赛生命周期”让流程控制清晰可维护运动会不是一锤子买卖它有明确阶段报名期 → 赛前检录 → 比赛中 → 成绩公示 → 颁奖典礼。每个阶段允许的操作不同报名期不能提交成绩比赛中不能修改报名信息硬编码状态判断if state competing: allow_submit()会让条件分支爆炸。状态模式把每个阶段封装成独立类状态切换即对象切换逻辑内聚。from abc import ABC, abstractmethod class CompetitionState(ABC): abstractmethod def register_animal(self, animal: str) - bool: pass abstractmethod def submit_result(self, result: dict) - bool: pass abstractmethod def close_competition(self) - CompetitionState: pass class RegistrationState(CompetitionState): def register_animal(self, animal: str) - bool: print(f 报名成功{animal}) return True def submit_result(self, result: dict) - bool: print(❌ 错误报名期不能提交成绩) return False def close_competition(self) - CompetitionState: print(➡️ 进入检录阶段...) return VerificationState() class VerificationState(CompetitionState): def register_animal(self, animal: str) - bool: print(❌ 错误检录期停止报名) return False def submit_result(self, result: dict) - bool: print(✅ 检录通过等待开赛...) return True def close_competition(self) - CompetitionState: print(➡️ 比赛开始) return CompetingState() class CompetingState(CompetitionState): def register_animal(self, animal: str) - bool: print(❌ 错误比赛中禁止新增选手) return False def submit_result(self, result: dict) - bool: print(f 成绩提交{result}) return True def close_competition(self) - CompetitionState: print(➡️ 比赛结束进入公示期...) return ResultAnnouncementState() class ResultAnnouncementState(CompetitionState): def register_animal(self, animal: str) - bool: print(❌ 错误公示期不接受报名) return False def submit_result(self, result: dict) - bool: print(❌ 错误公示期成绩已锁定) return False def close_competition(self) - CompetitionState: print( 颁奖典礼开始) return AwardCeremonyState() class AwardCeremonyState(CompetitionState): def register_animal(self, animal: str) - bool: return False def submit_result(self, result: dict) - bool: return False def close_competition(self) - CompetitionState: return self # 颁奖后结束 # 主控类持有当前状态 class AnimalOlympics: def __init__(self): self._state: CompetitionState RegistrationState() def register_animal(self, animal: str): return self._state.register_animal(animal) def submit_result(self, result: dict): return self._state.submit_result(result) def advance_phase(self): self._state self._state.close_competition() # 使用示例 olympics AnimalOlympics() olympics.register_animal(Cheetah) # 报名成功Cheetah olympics.submit_result({event: sprint, time: 9.58}) # ❌ 错误报名期不能提交成绩 olympics.advance_phase() # ➡️ 进入检录阶段... olympics.submit_result({event: sprint, time: 9.58}) # ✅ 检录通过等待开赛...逻辑说明RegistrationState、VerificationState等是具体状态类每个类只实现自己阶段允许的操作。AnimalOlympics主控类不写任何if判断只委托给当前_state对象。状态模式的本质是用多态替代条件分支。它让“当前能做什么”这件事由对象类型本身决定而不是由一堆if-else决定——这极大提升了流程变更的可维护性。要加“复活赛”阶段新增一个ResurrectionState类重写对应方法修改close_competition()的返回即可。3. 避坑指南动物运动会系统开发中踩过的5个真实深坑写这个系统时我和某高校的几位同学一起迭代了三版从“能跑通”到“能讲清楚设计模式”中间填了不少坑。这些不是教科书里的理论陷阱而是编译能过、运行报错、调试半小时才定位的实战血泪经验。列在这里帮你省下至少8小时。3.1 现象抽象工厂创建的动物实例调用run()方法时报AttributeError: Cheetah object has no attribute run原因抽象工厂只保证创建出“动物”但没约束动物必须有run()方法。如果Cheetah类忘了继承基类或实现接口工厂照常返回实例但下游代码调用时才崩溃。这是典型的“契约缺失”——工厂只管造不管造出来的东西合不合规矩。解决强制动物基类如Animal定义抽象方法。from abc import ABC, abstractmethod class Animal(ABC): abstractmethod def run(self): pass # 所有动物必须能跑哪怕只是象征性 abstractmethod def jump(self): pass # 工厂创建的类必须继承Animal并实现这些方法否则无法实例化3.2 现象策略模式切换后Competition类的scoring_strategy属性在某些路径下是None导致calculate_score()调用失败原因策略对象是构造时注入的但如果Competition实例被复用比如一个实例处理多场比赛而新比赛需要不同策略却没有重新设置scoring_strategy就会沿用旧策略或None。更隐蔽的是某些异常路径如初始化失败可能跳过策略赋值。解决在Competition的calculate_score()方法里加防御性检查并提供显式重置接口。def calculate_score(self, performance: float) - int: if self.scoring_strategy is None: raise RuntimeError(计分策略未设置请先调用set_scoring_strategy()) return self.scoring_strategy.calculate_score(performance) def set_scoring_strategy(self, strategy: ScoringStrategy): self.scoring_strategy strategy3.3 现象观察者模式下大屏显示延迟3秒才刷新而成绩系统秒级更新原因观察者回调函数里做了耗时操作如大屏的refresh()方法内部调用了慢速HTTP API或渲染复杂SVG阻塞了主线程导致后续观察者排队等待。观察者模式本身不保证异步所有回调默认同步执行。解决将耗时观察者逻辑移至后台线程或协程。import threading def dashboard_refresh_async(event, **kwargs): def _refresh(): # 这里放耗时的大屏更新逻辑 time.sleep(3) # 模拟慢速API print(f[大屏] 刷新完成{kwargs[athlete]}) threading.Thread(target_refresh).start() # 注册时用异步版本 judge.attach(dashboard_refresh_async)3.4 现象命令模式的undo()方法执行后成绩没恢复反而变成0原因RecordTimeCommand在execute()中保存了self._original_score self.judge.get_score(athlete)但get_score()返回的是当前值而record_time()可能已经修改了_scores字典。如果record_time()是直接赋值self._scores[athlete] time_sec那么get_score()拿到的就是新值_original_score存的就是错的。解决execute()中先读原始值再执行业务操作。def execute(self): # 关键先读再写 self._original_score self.judge.get_score(self.athlete) # 此时还是旧值 self.judge.record_time(self.athlete, self.time_sec) # 此后才覆盖3.5 现象状态模式下调用advance_phase()后submit_result()仍能成功提交状态没真正切换原因advance_phase()方法返回了新状态对象但没有赋值给self._state。常见低级错误写了return VerificationState()却忘了self._state self._state.close_competition()。解决在advance_phase()中强制赋值并添加日志确认。def advance_phase(self): old_state self._state.__class__.__name__ self._state self._state.close_competition() new_state self._state.__class__.__name__ print(f 状态切换{old_state} → {new_state})4. 文档生成与模式验证用PlantUML自动生成类图一眼看清设计质量光代码写对还不够大作业要求交文档而文档的核心是UML图——它不是装饰是设计意图的终极证明。手动画图费时易错且代码一改图就过期。我的做法是用代码注释驱动PlantUML自动生成类图再用图反向验证模式是否真正落地。这套流程让文档和代码永远一致还能快速发现设计漏洞。4.1 用Python docstring标注类职责为PlantUML提供元数据PlantUML能解析特定格式的注释。我们在关键类的docstring里用pattern标签声明其设计模式角色用collaborator声明协作关系。这比纯代码更直白地表达设计意图。class AnimalFactory(ABC): pattern Abstract Factory collaborator creates: Animal, Event desc 定义创建动物和项目实例的接口由具体工厂实现。 abstractmethod def create_runner(self) - Animal: pass class TimeBasedScoring(ScoringStrategy): pattern Strategy desc 实现基于时间的计分算法用于短跑、游泳等计时项目。 def calculate_score(self, time_sec: float) - int: return min(120, int(100 (10.0 - time_sec) * 10))4.2 编写脚本提取注释生成PlantUML类图代码写一个简短的Python脚本generate_uml.py扫描源码提取pattern、collaborator等标签拼成PlantUML语法。核心逻辑是正则匹配和字符串模板。import re import glob def extract_patterns_from_file(filepath): with open(filepath, r, encodingutf-8) as f: content f.read() # 匹配类定义和其docstring class_pattern rclass\s(\w)\s*(\(.*?\))?:\s*([\s\S]*?) matches re.findall(class_pattern, content) uml_lines [startuml, skinparam classAttributeIconSize 0] for class_name, _, doc in matches: # 提取pattern pattern_match re.search(rpattern\s(.), doc) pattern pattern_match.group(1) if pattern_match else Unknown # 提取collaborator collab_match re.search(rcollaborator\screates:\s(.), doc) collaborators collab_match.group(1).split(,) if collab_match else [] # 生成PlantUML类定义 uml_lines.append(fclass {class_name} as {class_name} {{) uml_lines.append(f {pattern}) uml_lines.append(}) # 生成协作关系creates for collab in collaborators: collab collab.strip() if collab: uml_lines.append(f{class_name} -- {collab} : creates) uml_lines.append(enduml) return \n.join(uml_lines) if __name__ __main__: uml_code extract_patterns_from_file(animal_olympics.py) with open(design.uml, w, encodingutf-8) as f: f.write(uml_code) print(✅ PlantUML代码已生成design.uml)4.3 用PlantUML工具链一键渲染获得专业级类图生成design.uml后用PlantUML官方jar包渲染无需安装下载一个jar就行# 下载 plantuml.jar官网最新版 wget https://sourceforge.net/projects/plantuml/files/plantuml.jar/download -O plantuml.jar # 渲染为PNG java -jar plantuml.jar design.uml # 或渲染为SVG矢量图缩放不失真 java -jar plantuml.jar -tsvg design.uml渲染出的类图会清晰显示所有带Abstract Factory标签的类如AnimalFactory所有带Strategy标签的类如TimeBasedScoringAnimalFactory箭头指向Animal和Event表示“创建”关系Competition类与ScoringStrategy之间有依赖箭头因构造函数注入。验证技巧拿到这张图立刻问自己三个问题模式数量对不对图中是否出现了题目要求的“多种”模式至少4种少于4种说明有模式没落地。关系是否合理Abstract Factory是否只指向它创建的产品Animal/Event而不指向ScoringStrategy如果指向了说明工厂职责越界该拆。耦合度高不高Judge类是否只依赖Subject观察者基类而不直接依赖ScoreSystem或Dashboard如果有直接依赖箭头说明观察者模式没生效得查attach()调用是否遗漏。这张图就是你的设计“X光片”它不撒谎。4.4 进阶技巧用单元测试验证模式行为而非仅验证功能很多同学只测“输入9.58输出120分”这只能证明功能正确不能证明模式正确。真正的模式验证是测它的结构特性。例如验证策略模式是否可插拔import unittest from unittest.mock import Mock class TestScoringStrategy(unittest.TestCase): def test_strategy_can_be_swapped_at_runtime(self): # 创建两个不同策略 time_strategy TimeBasedScoring() height_strategy HeightBasedScoring() # 初始化比赛用时间策略 comp Competition(time_strategy) self.assertEqual(comp.record_result(9.58), 120) # 时间策略生效 # 动态切换为高度策略 comp.scoring_strategy height_strategy self.assertEqual(comp.record_result(2.45), 475) # 高度策略立即生效 # 验证切换后原策略不再被调用 time_strategy.calculate_score Mock(return_value0) self.assertEqual(comp.record_result(2.45), 475) # 仍走height_strategyMock未被调 if __name__ __main__: unittest.main()关键点这个测试不关心“9.58秒到底该得多少分”而关心“换策略后计算逻辑是否真的变了”。它验证了策略模式的核心价值运行时可替换。同理可以写测试验证工厂创建的动物是否真的属于指定家族isinstance(cheetah, TerrestrialAnimal)观察者注册后notify()是否真的触发了所有回调用Mock断言调用次数命令undo()后状态是否精确回滚到执行前断言get_score()返回值。模式验证的黄金法则是不测“结果”而测“行为特征”。这是我带某实验室学生做项目时总结的教训——功能测试保正确性模式测试保设计质量。5. 交付与答辩如何把“动物运动会”讲成一个让人眼前一亮的设计故事大作业最终要交.zip包但真正决定分数的是你在答辩时如何讲述这个系统。别一上来就说“我用了5个设计模式”那像背课文。我的建议是用一个贯穿始终的“痛点-解法-证据”故事线把技术选择变成有温度的决策过程。下面是我帮A同学打磨的答辩话术框架他拿了满绩。5.1 开场用一个具体翻车场景抓住注意力“老师好我做的动物运动会系统初衷特别简单让狮子报名短跑后能无缝兼项跳高。但第一版代码上线测试时发生了这样一幕——当测试同学尝试让‘海豚’参加‘跨栏’项目系统直接抛出AttributeError。我们花了2小时才定位到因为跨栏逻辑硬编码在LandEvent类里而海豚是AquaticAnimal根本没继承这个类。那一刻我意识到这不是功能缺陷是设计缺陷。动物和项目之间的耦合太紧任何跨生态的尝试都会翻车。于是我决定用设计模式给系统‘动一场结构性手术’。”5.2 主干用“问题驱动”串联模式突出决策理由不要罗列模式而是说“为了解决XX问题我选了YY模式因为ZZ原因”。例如问题新增“飞行动物组”时要改遍所有if animal.type terrestrial的判断。解法引入抽象工厂。理由工厂把“创建谁”的决策集中到一处新增组只需加一个工厂类其他代码不动。证据展示AerialFactory代码和UML图中新增的工厂箭头。问题裁判反馈“计分规则三天两头变每次改都要提心吊胆怕影响其他项目”。解法用策略模式封装计分。理由规则变成可插拔的组件换规则就像换电池。证据演示运行时切换策略IDE控制台实时输出不同得分。问题颁奖典礼前10分钟大屏突然黑屏运维发现是成绩系统日志刷爆了磁盘。解法用观察者模式解耦。理由大屏只订阅自己关心的事件成绩系统不再为它生成冗余日志。证据对比改造前后日志量用wc -l命令截图。5.3 高光展示一个“模式协同”的复合场景体现深度单个模式容易难的是组合。挑一个最能体现协同的场景——比如“复活赛”的实现“复活赛是个复合需求它需要新动物工厂ResurrectionFactory新计分策略SecondChanceScoring新状态ResurrectionState还要让原有裁判能触发复活指令命令模式。我用命令模式封装‘发起复活赛’操作它内部会1切换工厂2切换策略3切换状态。这样一个按钮点击就完成了四个模式的协同切换。这证明模式不是孤岛而是可以编织成网的。”5.4 收尾用一句“后悔药”收住留下专业印象最后别忘升华。我说的是“如果现在让我重做这个系统我唯一会加的‘后悔药’就是在项目初期就写好PlantUML生成脚本。因为设计图不是交差的文档而是写代码时的导航仪——它让我在敲下第一行class AnimalFactory之前就看清了整个系统的骨架。希望帮到你。”本文还有配套的精品资源点击获取