抽象与建模能力详解:边界与契约 抽象与建模能力详解:边界与契约以 C# 上位机(工业设备监控/控制软件)为背景,用 DDD 思路讲清楚边界(Boundary)与契约(Contract),并给出可运行的完整代码。一、先给结论:什么是边界,什么是契约概念一句话定义解决的问题上位机中的体现边界把系统切成"谁负责什么"的范围划分复杂度隔离、团队分工、依赖方向设备通信层 / 业务逻辑层 / UI 层;核心域 / 支撑域 / 通用域契约边界之间交互的"约定",一旦定下不可随意改模块解耦、可替换、可测试接口IDeviceDriver、事件DeviceDataReceived、DTO 数据结构关键关系:先划边界 → 再定契约 → 契约是边界的"接口面"。二、边界:DDD 的三类子域 + 上位机的分层2.1 战略设计视角:核心域 / 支撑域 / 通用域以一个"多设备工业上位机"为例:上位机系统 ├── 核心域 (Core Domain) ← 公司的核心竞争力,要自研、要投入最强的人 │ └── 工艺配方引擎 / 报警决策引擎 / 数据采集调度算法 │ ├── 支撑域 (Supporting Domain) ← 业务需要但不核心,可自研也可外包 │ └── 设备管理 / 用户权限 / 报表 │ └── 通用域 (Generic Domain) ← 行业通用,直接买/用开源 └── 日志 / 串口通信 / 数据库ORM / 消息队列识别边界的三个提问法:这段代码改了会影响公司赚钱吗?→ 会 = 核心域这段代码换个项目还能用吗?→ 能 = 通用域这段代码是为某个业务场景定制的吗?→ 是 = 支撑域2.2 战术设计视角:限界上下文(Bounded Context)上位机常见的限界上下文:┌─────────────────────────────────────────────────────┐ │ 上位机应用 │ │ │ │ ┌──────────────┐ ┌──────────────┐ │ │ │ 设备接入上下文 │ │ 工艺执行上下文 │ │ │ │ (Device BC) │──▶│ (Recipe BC) │ │ │ └──────────────┘ └──────────────┘ │ │ │ │ │ │ ▼ ▼ │ │ ┌──────────────┐ ┌──────────────┐ │ │ │ 报警上下文 │ │ 数据持久化BC │ │ │ │ (Alarm BC) │ │ (Storage BC) │ │ │ └──────────────┘ └──────────────┘ │ └─────────────────────────────────────────────────────┘每个上下文内部:可以有自己的模型、自己的语言、自己的数据库。上下文之间:只能通过契约交互(不能直接引用对方的内部类)。2.3 上位机的物理分层边界(依赖方向)┌─────────────────────────────────────┐ │ UI 层 (WinForm/WPF) │ ← 只依赖 Application ├─────────────────────────────────────┤ │ Application 层 (用例编排) │ ← 只依赖 Domain + 抽象 ├─────────────────────────────────────┤ │ Domain 层 (核心域模型) │ ← 不依赖任何外部!纯 C# ├─────────────────────────────────────┤ │ Infrastructure 层 (串口/TCP/DB) │ ← 实现 Domain 定义的接口 └─────────────────────────────────────┘ ↑ 依赖倒置:Infra 依赖 Domain 的接口,而不是反过来边界铁律:Domain 层不能using System.IO.Ports。串口是基础设施,Domain 只认IDeviceChannel接口。三、契约:三种形态3.1 接口契约(Interface Contract)定义"我能做什么",隐藏"我怎么做的"。// 位于 Domain 层 —— 不依赖任何实现publicinterfaceIDeviceDriver{stringDeviceId{get;}TaskConnectResultConnectAsync(CancellationTokenct);TaskWriteResultWriteAsync(DeviceCommandcommand,CancellationTokenct);eventEventHandlerTelemetryReceivedEventArgsTelemetryReceived;}3.2 事件契约(Event Contract)定义"发生了什么",用于上下文之间异步解耦。// 领域事件:设备遥测到达(不包含"谁实现的"信息)publicsealedrecordTelemetryReceived(stringDeviceId,DateTimeOffsetTimestamp,IReadOnlyDictionarystring,doubleValues);3.3 数据结构契约(DTO / Value Object)定义"数据长什么样",跨边界传输。// 值对象:温度+单位,自校验publicreadonlyrecordstructTemperature{publicdoubleValue{get;}publicstringUnit{get;}publicTemperature(doublevalue,stringunit){if(unit!="C"unit!="F")thrownewArgumentException("非法单位");Value=value;Unit=unit;}}契约设计五原则:窄接口:一个接口只做一件事(IReader/IWriter分开)稳定:契约一旦发布,只能加不能改(向后兼容)无泄漏:契约里不出现实现细节(不暴露SerialPort类型)值语义:跨边界传值对象/DTO,不传实体引用显式失败:用ResultT而非抛异常跨边界四、完整代码案例:多设备采集上位机4.1 项目结构UpperComputer/ ├── UpperComputer.Domain/ # 核心域(无外部依赖) │ ├── Devices/ │ │ ├── IDeviceDriver.cs # 契约:设备驱动 │ │ ├── DeviceCommand.cs # 契约:命令 DTO │ │ ├── TelemetryReceived.cs # 契约:事件 │ │ └── DeviceId.cs # 值对象 │ ├── Alarms/ │ │ ├── IAlarmRule.cs # 契约:报警规则 │ │ └── AlarmEvaluator.cs # 核心域逻辑 │ └── Result.cs # 通用结果类型 │ ├── UpperComputer.Infrastructure/ # 基础设施(实现契约) │ ├── SerialDeviceDriver.cs # 实现 IDeviceDriver(串口) │ ├── TcpDeviceDriver.cs # 实现 IDeviceDriver(TCP) │ └── SqliteAlarmRepository.cs │ ├── UpperComputer.Application/ # 应用层(用例编排) │ └── TelemetryPipeline.cs │ └── UpperComputer.UI/ # WinForm 上位机界面 └── MainForm.cs4.2 Domain 层:定义契约(不依赖任何东西)// ============ Result.cs ============namespaceUpperComputer.Domain;publicreadonlyrecordstructResultT{publicboolIsSuccess{get;}publicT?Value{get;}publicstring?Error{get;}privateResult(boolok,T?value,string?error){IsSuccess=ok;Value=value;Error=error;}publicstaticResultTOk(Tvalue)=new(true,value,null);publicstaticResultTFail(stringerror)=new(false,default,error);}// ============ DeviceId.cs ============namespaceUpperComputer.Domain.Devices;publicreadonlyrecordstructDeviceId(stringValue){publicstaticDeviceIdParse(strings)=string.IsNullOrWhiteSpace(s)?thrownewArgumentException("DeviceId 不能为空"):new(s);}// ============ DeviceCommand.cs ============namespaceUpperComputer.Domain.Devices;/// summary契约:一条下发给设备的命令(值对象,不可变)/summarypublicsealedrecordDeviceCommand(stringOpCode,IReadOnlyDictionarystring,stringParameters){publicstaticDeviceCommandReadRegister(intaddress)=new("READ",newDictionarystring,string{["addr"]=address.ToString()});}// ============ TelemetryReceived.cs ============namespaceUpperComputer.Domain.Devices;/// summary契约:遥测事件(跨边界传值,不含实现)/summarypublicsealedrecordTelemetryReceived(DeviceIdDeviceId,DateTimeOffsetTimestamp,IReadOnlyDictionarys