智慧养老平台面向对象设计:OOA到OOD的完整实践指南

发布时间:2026/10/15 7:47:43
智慧养老平台面向对象设计:OOA到OOD的完整实践指南 简介软件工程课程设计所需的智慧养老服务平台面向对象分析与设计资料以一次完整的综合性实验报告形式展示了面向对象分析与设计方法在真实项目中的落地。资源仅包含1个doc文档压缩包大小414KB内容围绕UML建模展开先根据老人、家属、医护人员、管理员四类核心参与者划分用例图再给出系统类图逐类列出属性和服务并解释依赖、聚合、组合、泛化、关联等关系最后针对紧急联系、在线咨询等关键系统行为绘制顺序图体现对象间的动态协作。整份资料覆盖从需求理解、对象模型构建到交互流程设计的完整链路可作为软件工程课程设计、UML建模实验或毕业设计的重要参考。已有103人学习文档结构与图表完整读者可直接借鉴用例划分、类关系设计和核心场景时序描述快速整理出自己项目的面向对象分析与设计章节。1. 智慧养老平台的面向对象设计为什么对象选错项目就废智慧养老服务平台里最容易被低估的环节就是面向对象设计和分析OOAD。我见过不少课程项目把精力全放在页面美观和数据库表结构上用例图画成蜘蛛网类图用一堆箭头把所有类连在一起答辩时一问“这个类为什么存在”全场沉默。这个项目的复杂度恰恰不在界面而在业务语义老人的健康数据是连续事件流告警要在多个角色之间流转护工任务有状态机家属需要被动触达。这些语义如果不经过分析直接落到代码后面就是满屏魔鬼数字、上帝类和改一处崩三处的连锁反应。这篇文章把 OOA 和 OOD 拆开讲角色和用例怎么定领域模型怎么收敛紧急救助这类复杂功能在对象层面如何设计才能扛住需求变更以及我踩过的那些坑。2. 面向对象分析从角色和用例开始先回答“谁在用、要什么”OOA 这步最容易走样。很多人拿到需求就开画用例图把“登录”“注册”“查看个人信息”全堆上去图很丰满但业务边界完全看不出来。我的经验是先别碰图先用“角色 × 业务目标”把需求盘清楚再来谈用例。2.1 角色识别先分主角色和涉众别让管理员包揽一切智慧养老平台的用户列表看起来简单老人、家属、护工、管理员。但仔细想老人真的会去操作 App 吗多数场景里老人面对的是一个带一键呼救的终端设备复杂的查询和设置是由家属代操作的。这直接影响角色模型的划分老人到底是一个 Actor还是只是一个“被监护对象”我一般把角色分成三类直接操作系统的人、接收通知或结果的人、外部系统。老人终端是一个 Actor家属既是操作者又是接收者护工是任务执行者管理员是资源调度者短信网关和穿戴设备是外部系统 Actor。这一步最常见的翻车点是让“管理员”一个角色承担所有后台功能。登录、派单、排班、权限管理、数据统计全挂在管理员名下用例图画出来就是一张大蜘蛛网。我习惯的做法是先列一个角色和核心诉求的对应表每个角色只回答两个问题我给平台带来什么信息平台还给我什么结果。角色核心诉求主要交互方式老人终端一键呼救、被看护终端设备、弱交互 App家属接收告警、查看健康趋势App、消息推送护工接收任务、填报服务记录工作台 App管理员派单、排班、资源调配、查统计管理后台外部系统短信网关等接收触达请求、返回发送状态接口这张表不是为了画图好看而是为了后面定用例边界。老人终端作为独立 Actor它的用例就聚焦在“发起呼救”这类动作上家属的用例聚焦在“接收通知”“查看报告”上。角色表里多写的每一行都能避免后面把互不相干的业务流程硬塞进一个类。2.2 用例粒度一个用例必须通过“可观测价值测试”写智慧养老平台用例时我最爱盯的是粒度。判断标准就一条这个用例是不是给了某个角色一个可观测的结果。“登录”不是业务用例只是其他用例的前置条件“发送短信”也不是用例是通知动作的一环。“处理紧急告警”才是用例“一键呼救”也是用例因为老人终端按下按钮后系统产生了告警并进入处理流程。初学经常纠结“一键呼救”和“派发护工任务”是两个用例还是一个。关键看主执行者一键呼救由老人终端发起派发任务由管理员或系统完成它们服务的业务目标不同应该拆成两个用例。另一个高频问题是 include 和 extend 分不清。我的记法很简单完整流程里被公共复用的步骤用 include满足特定条件才追加的步骤用 extend。“鉴权”被所有后台操作复用是 include“告警长时间无人处理升级通知家属”是基础流程之外的可选分支用 extend。这里有个需要注意的细节用例名尽量写成“动词 宾语”不要用“告警管理”这种模糊名词。是“处理告警”还是“查看告警列表”写清楚评审的人和后来接手的人都不会猜。2.3 用 PlantUML 把用例图画出来画图工具我常用 PlantUML因为它是文本描述能进版本库改起来比画图软件快得多。下面是一个聚焦“紧急救助”流程的用例图我把无关的辅助用例全部去掉只保留核心业务流程。startuml left to right direction actor 老人终端 as Elder actor 护工 as Caregiver actor 管理员 as Admin actor 家属 as Family rectangle 紧急救助 { usecase 一键呼救 as UC1 usecase 生成告警 as UC2 usecase 派发护工任务 as UC3 usecase 处理告警 as UC4 usecase 升级通知家属 as UC5 } Elder -- UC1 Caregiver -- UC4 Admin -- UC3 Family -- UC5 UC1 . UC2 : include UC2 . UC3 : include UC4 . UC5 : extend enduml这个图说明几件事第一老人终端的用例只有“一键呼救”其余都是系统后台响应第二“生成告警”被“一键呼救”包含但未来如果穿戴设备检测到心率异常也能生成告警这个用例就可以被另一个前端用例复用第三“升级通知家属”是“处理告警”的扩展只有超时未处理才触发。依赖方向一定要画对include 箭头从基础用例指向被包含的公共用例extend 箭头从扩展用例指向被扩展的基础用例。很多项目把图里的 Actor 换成“用户”这是另一个问题。不同角色对同一功能的权限和结果完全不同Actor 一旦合并成“用户”后面定权限模型时就要返工。2.4 用例规约表画完图只是开始把主事件流写清楚用例图只是索引真正值得评审的是用例规约。我一般只要求对核心业务用例写规约比如“一键呼救”和“处理告警”辅助用例不写。规约表聚焦在主事件流和异常路径这是后面设计类的方法和状态的关键输入。项目内容用例名称一键呼救前置条件老人终端已绑定平台设备在线主事件流1. 老人按下呼救按钮2. 系统生成紧急告警记录3. 系统按策略派发护工任务4. 系统通知家属5. 系统回显“已受理”异常路径2a. 设备离线记录缓存并在设备恢复后补传3a. 护工 5 分钟内未接单任务进入等待池4a. 家属通知失败改为短信重试后置条件告警状态为“处理中”工单已创建通知流水已记录写规约表时需求里的矛盾会自己暴露出来。比如老人按下呼救按钮后系统是“通知家属”还是“通知管理员”这两个动作发生在不同环节。再比如健康数据阈值触发告警是不是也走“一键呼救”这条用例我倾向于拆成独立用例“健康异常告警”因为它的发起者是系统而不是老人终端主事件流完全不同。把这些边界在规约表里划清楚后面做类设计心里就有底了。3. 领域模型与类图识别对象、判定关系再用伪代码验证归属用例规约搞定后进入从 OOA 到 OOD 的过渡环节领域建模。类图的核心不是画框而是决定谁拥有数据、谁负责行为、谁依赖谁。这一步做得糙后面所有设计模式都救不回来。3.1 用“三关”过滤候选类把值对象和实体分开从需求里提取名词是最容易的但也是最容易过度建模的。老人、护工、家属、健康记录、告警、工单、设备、房间、短信、费用都能成为候选类。我一般给这些名词过三关第一关领域相关性。这个名词是否承载业务规则比如“短信”只是触达手段它的状态和费用审计虽然有意义但不应成为核心领域类把它放基础设施层就好“告警记录”承载了级别、状态、升级规则必须是核心类。第二关生命周期。它是否独立存在并有状态变化“房间”如果只是老人的一个属性可以不当类但如果要做床位调度和资源统计它就有独立生命周期得升级为类。第三关行为责任。它除了存数据有没有业务行为没有行为的先做值对象。以健康数据为例。“一次测量记录”里包含心率、血压、测量时间这些数据本身没有独立生命周期随记录删除而消失应建模为值对象。“老人的健康档案”承载了历史记录、趋势计算、风险评估行为是实体。新手常见错误是给“心率”“血压”各建一个类然后这些类之间再拉关系模型瞬间就失控了。3.2 第一版类图先画核心域不追求一次画完整类图不要一步到位。我会先只画老人、亲属、护工、健康记录、告警、工单这六个核心类把它们之间的关系定清楚。下面是聚焦监护与救助域的第一版类图。startuml class Elderly { - id: String - name: String - roomNo: String bindDevice(device): void addHealthRecord(record): void receiveAlert(alert): void } class HealthRecord { - id: String - measureTime: Date - recordType: String - value: double - unit: String } class AlertRecord { - id: String - level: String - status: String - createTime: Date bindOrder(order): void escalate(): void } class ServiceOrder { - id: String - orderType: String - status: String - assigneeId: String } class Caregiver { - id: String - name: String - workload: int receiveOrder(order): void } class Guardian { - id: String - name: String - relation: String } Elderly *-- HealthRecord Elderly 1 o-- 0..* AlertRecord : 产生 AlertRecord 1 -- 0..1 ServiceOrder : 触发 Caregiver 1 o-- 0..* ServiceOrder : 承接 Elderly 1 -- 0..2 Guardian : 关联 enduml画这个图时最重要的不是类有多少而是菱形箭头用对。健康记录和老人之间是组合关系一条健康记录不能脱离老人存在老人删除记录级联删除。告警和老人是聚合关系告警虽然由老人关联产生但它是独立创建的审计对象老人实体删除后告警记录仍需保留所以用空心菱形。护工和工单之间也是聚合护工离职历史工单不能丢。老人和家属只是关联二者通过引用互相访问生命周期互不控制。3.3 关系判断遇到拿不准的箭头用生命周期回答类图上最容易翻车的就是关系箭头。我的判断方法就一句话谁创建、谁销毁、谁只是临时用一下。如果 A 创建了 BB 的销毁由 A 决定这就是组合。组合在代码里通常表现为成员变量在构造方法里直接 new。如果 A 持有 B 的引用但 B 可以独立存在这是聚合B 一般由外部创建后注入给 A。如果只是方法参数、方法返回值或者局部变量里用了一下这是依赖不该画成关联实线。很多项目把所有关系都画成一根直线结果别人根本分不清“拥有”和“使用”评审直接就废了。判断聚合还是组合有个实践经验问自己一句“这个子对象被删掉后主对象还能不能正常工作”。老人没有健康记录依然是一个可以被识别的老人所以严格说健康记录组合于老人是业务规则上的强绑定护工没有工单依然是护工但一个 AlertRecord 如果没有关联任何老人这条告警就没有存在意义。所以告警和老人更应该偏向组合或强聚合。这种细枝末节的争论在项目里很常见我的建议是别在画图上耗太久代码里怎么创建和删除关系就怎么画保持图和代码一致。3.4 用伪代码验证第一版关系画完类图我习惯马上写一小段伪代码来验证这些关系能不能落地。不需要写完整业务只写构造方法和关键关联能把箭头含义表达清楚就够了。class Elderly: def __init__(self, elder_id: str, name: str): self.id elder_id self.name name self.health_records [] # 组合老人直接管理记录列表 self.alerts [] # 聚合/强关联告警由外部系统创建后挂接 def add_health_record(self, record: HealthRecord) - None: record.owner_id self.id self.health_records.append(record) def receive_alert(self, alert: AlertRecord) - None: self.alerts.append(alert) class AlertRecord: def __init__(self, alert_id: str, level: str): self.id alert_id self.level level self.status pending self.order None # 触发工单允许为空 def bind_order(self, order: ServiceOrder) - None: self.order order order.alert_id self.id def escalate(self) - None: if self.status pending: self.status escalated class Caregiver: def __init__(self, caregiver_id: str, name: str): self.id caregiver_id self.name name self.orders [] def receive_order(self, order: ServiceOrder) - None: self.orders.append(order) order.assignee_id self.id这段伪代码能暴露不少问题。比如AlertRecord 里如果再存一个 elder_id同时又通过老人列表去索引告警就产生了双向冗余引用后续同步很容易出错。我的习惯是组合关系由父对象持有子对象列表聚合关系由子对象持有父对象或外部传入的关联 ID尽量减少双向引用。Caregiver 里保存 orders 列表ServiceOrder 里又保存 assignee_id这其实是必要的双向查询但要注意它的同步边界由一个统一的服务方法去维护而不是让业务代码到处改。伪代码验证还有一个附加收益它让你提前发现一个类是“数据袋子”还是“行为体”。如果某个类写下来只有 getter 和 setter没有任何行为方法你就要反思它的职责是不是被外包给了别处。4. 面向对象设计落地SOLID、设计模式与分层让类图扛得住改动OOA 解决“有什么”OOD 解决“怎么改”。智慧养老平台最怕的不是第一版跑不起来而是三个月后的需求变更告警规则要加血氧通知渠道要加微信派单策略要从人工改成自动。类设计如果一开始就没有预留扩展点每次改动都是拆东墙补西墙。4.1 用 SOLID 过一遍候选类重点盯 SRP 和 OCP完成领域模型后我会把每个类都过一遍 SOLID。最容易出问题的是前两条。单一职责原则处理的是“一个类为什么变化”。如果 AlertRecord 既要记录告警又要负责通知家属还要负责派单它就有三个变化原因后面动任何一处都得改它。正确做法是AlertRecord 只负责告警事实和状态流转通知行为交给通知服务派单行为交给派单服务。开闭原则处理的是“加需求时改不改老代码”。新加一个“血氧告警规则”不应该打开风险判定类去加 if而是应该新增一个策略实现类。把这两个原则落地靠的不是自觉而是类和接口的划分。一个类里如果出现了“if type ... then”这种分支就该停下来想想这是在违反开闭原则吗。4.2 三个值得优先考虑的设计模式策略、观察者、状态智慧养老平台里我认为优先级最高的是三个模式策略模式、观察者模式、状态模式。不要求全用但核心业务场景几乎绕不开。策略模式用在哪健康风险的判定。心率、血压、血氧的判定规则差异很大而且未来一定增长。把判定逻辑做成接口每种指标给一个实现类新增规则就不必动原有代码。观察者模式用在哪告警通知。一次告警发生后家属要收到推送护工工作台要刷新管理后台要弹窗短信网关可能要补发。如果把这些订阅者写死在告警类里类之间的耦合会爆炸。状态模式用在哪服务工单的状态流转。待派单、处理中、已完成、已升级每个状态下行为不同状态迁移有规则约束。下面给一个策略模式的落地示例用 Python 写其他语言同理。from abc import ABC, abstractmethod class RiskStrategy(ABC): abstractmethod def evaluate(self, record) - str: pass class HeartRateStrategy(RiskStrategy): def evaluate(self, record) - str: if record.value 120 or record.value 50: return high return normal class BloodPressureStrategy(RiskStrategy): def evaluate(self, record) - str: if record.systolic 160 or record.diastolic 100: return high return normal class AlertService: def __init__(self, strategy: RiskStrategy): self.strategy strategy def set_strategy(self, strategy: RiskStrategy) - None: self.strategy strategy def analyze(self, record) - None: level self.strategy.evaluate(record) if level high: self._create_alert(record)这个示例里有几个关键点。RiskStrategy 接口是扩展点新增指标只需要加一个实现类AlertService 完全不用改。set_strategy 让运行策略可以在运行时切换这个特性在未来做“老年人个性化阈值”时会用到一个老人心率正常区间是 50 到 80另一个老人可能是 60 到 90策略实例不同而已。还要注意不是所有地方都适合上策略。如果只有一种判定规则或者判定就是一个 if 的事硬套策略模式反而增加阅读负担。我的原则是至少出现两种规则且明确知道第三种在路上才上模式。观察者模式的代码量也不大。class AlertNotifier: def __init__(self): self._subscribers [] def subscribe(self, subscriber) - None: self._subscribers.append(subscriber) def notify(self, alert) - None: for subscriber in self._subscribers: subscriber.update(alert) class FamilyNotifier: def update(self, alert) - None: push_service.send(alert.guardian_id, alert.level) class CaregiverBoardNotifier: def update(self, alert) - None: board_service.refresh(alert.elderly_id, alert.level)观察者模式带来的直接好处是新增加一个“语音电话告警”渠道只需要新写一个订阅者类在配置里订阅即可AlertNotifier 一行不动。需要注意订阅者如果太多在单线程模型下会阻塞主流程。课程项目里可以先同步调用但设计上要留出异步扩展的空间比如把 notify 里的调用封装成可替换的异步执行器。4.3 分层与依赖倒置领域层不依赖框架类图设计完成后还要回答一个问题这些类放在哪一层。我常用的 OOD 落地分层是 controller、service、domain、repository、infrastructure。依赖方向是单向的controller 依赖 serviceservice 依赖 domain 和 repository 接口domain 不依赖任何框架infrastructure 实现 repository 接口并依赖 domain。src/ ├── controller/ # 接口层HTTP/消息入口参数校验 ├── service/ # 应用服务用例流程编排事务边界 ├── domain/ # 实体、值对象、策略接口、观察者接口 ├── repository/ # 仓储接口与实现持久化抽象 └── infrastructure/ # 短信、推送、数据库、缓存等具体实现这个结构解决的是依赖倒置问题。告警服务需要保存告警它不直接依赖数据库而是依赖 AlertRepository 接口数据库的具体访问在 infrastructure 里实现。以后从 MySQL 换成别的存储service 和 domain 层完全不用动。这也是答辩时最容易被认可的细节你能说出依赖方向为什么是单向的就说明不是照着模板画的图。我见过很多项目把 SQL 语句写在实体类里或者把 HTTP 请求对象传进 domain 层这种设计当时跑得通但一定是后续重构的重灾区。实体类只表达业务规则不感知框架这条红线不能退。5. 避坑指南智慧养老平台 OOD 的 5 个翻车现场与排查思路这段内容来自我做同类项目积累的排查经验。每条都不是理论推演而是真实发生过的翻车场景按“现象、原因、解决”来写。5.1 “上帝类”管理员一个类接管所有判断现象管理员类里有登录验证、派单、排班、统计报表、审核健康报告几十个方法。代码能跑但每次加功能都要改这个类测试用例越写越痛苦。原因初期为了省事把所有后台能力都挂在“管理员”这个角色下角色模型和业务服务混为一谈。解决把“管理员”拆成三个部分管理员实体只保存身份和权限数据登录、派单、统计分别下沉到独立的服务类服务类之间通过依赖注入协作不在实体里互相 new。排查方法很简单打开类文件如果一个类的方法超过 10 个且功能互不相关就先画它的调用图然后把按动词分组的方法拆出去。5.2 把健康数据当成一个静态字段现象老人实体里放了 heart_rate、blood_pressure、blood_oxygen 三个字段每次新测量直接覆盖。结果历史趋势查不了医生需要回看数据时只能查日志表。原因把“老人当前最新状态”和“身体测量历史”混为一个概念图省事用字段覆盖。解决健康数据建模为 HealthRecord 列表每次测量生成一条记录最新状态通过查询或缓存计算得出不单独存字段。如果确实需要快速拿到最新一条可以在 Elderly 上维护 latest_health_record_id 作为冗余但冗余的更新必须由同一个服务方法维护。判断模型是否合理有个简单办法问一句“我的医生要我提供这个老人过去三个月的血压变化我的类设计能回答吗”。5.3 继承被当成代码复用神器现象心率告警、血压告警、跌倒告警都继承同一个 Alert 基类基类里堆了通知、派单、升级的公共代码。后来不同告警的处理逻辑差异越来越大基类每次改动影响全部子类。原因看到不同告警有相同的字段和方法第一反应是继承抽取公共代码忽略了它们的行为差异。解决组合优先于继承。告警数据结构和状态流转可以统一但风险判定和通知行为用策略、观察者组合进去。只有满足“is-a”关系并且扩展点是明确的才考虑继承。排查方法如果子类重写了父类的大部分方法或者父类方法里满是 if 分支这个继承结构就该重构了。5.4 用例图画成大而全的蜘蛛网现象一张用例图放 50 个用例每个角色都和大部分用例相连评审时完全看不出平台的核心业务是什么答辩被问到“这个用例为什么存在”时答不上来。原因没有按业务目标划分视角把辅助功能和核心业务混在一张图里。解决每张用例图只聚焦一条核心业务流程比如“紧急救助”或“服务工单管理”登录、权限、日志这些支撑功能单独维护用例规约只对核心业务用例写。排查方法数一下图里非 Actor 的用例数量超过 12 个就拆分。另外如果图中某个用例没有明确的 Actor大概率是系统内部动作不该出现在用例图上。5.5 类图箭头乱飞聚合组合分不清现象类图上所有关系全部画成普通实线没有空心菱形和实心菱形或者菱形用错导致无法判断对象生存周期代码里对象创建和销毁混乱。原因跳过了生命周期分析把“某个方法里用了一下”理解成了“拥有”。解决用生命周期判断三种关系。A 创建并销毁 BB 不能独立存在用组合代码里表现为成员变量在构造里直接 newA 持有 B 引用但 B 可独立存在用聚合B 从外部注入只在方法参数或返回值里出现用依赖不画实线。排查方法逐条检查类图凡是你不能回答“两者的生存周期谁控制谁”的连线先删掉等代码里明确了再画。我的经验是画错菱形比不画更危险因为它传递了错误语义。6. 进阶用状态图锁死工单流转用顺序图锁死调用时序如果类图已经稳定我建议再往前走一步把动态行为也用 UML 定下来。这一步常被跳过但它在实现阶段的价值极高。它解决的痛点是类图能表达有什么对象但表达不了对象之间按什么顺序协作、状态按什么规则迁移。6.1 状态图优先给“紧急告警处理”画。告警和工单的状态流转是平台里最容易产生 bug 的地方用文字描述需求时各说各话用状态图一画分歧立刻消失。核心状态就是待处理、处理中、已完成、已升级这几个转移条件分别是接单、填报完成、超时未处理。startuml state 待处理 as pending state 处理中 as processing state 已完成 as done state 已升级 as escalated pending -- processing : 护工接单 processing -- done : 护工提交处理结果 pending -- escalated : 超时未接单 processing -- escalated : 处理超时 enduml这张图的价值在于每个状态迁移都必须对应一个明确的触发动作和代码入口。超时未接单谁触发是定时任务还是用户操作状态图一旦明确实现时就只剩翻译工作。6.2 顺序图定“一键呼救”的调用链路。我更习惯只列关键消息序列不画得太细节老人终端请求、告警服务创建记录、策略风险评估、通知订阅者、生成工单。顺序图先于代码存在的意义是它把接口签名提前固定了。AlertService 需要哪些方法、RiskStrategy 返回什么类型、AlertNotifier 通知谁的顺序在编码前就达成一致多人协作时不至于天天为接口吵架。6.3 最后是我的评审自查习惯每画完一版设计花十分钟检查类图的依赖方向是否单向、有没有不必要的双向引用、策略和观察者模式是否真的必要。我现在的习惯是先写状态图和顺序图再回头完善类图这个顺序救过我很多次尤其在工单流转这类状态密集型功能上。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询