在领域驱动设计(Domain-Driven Design, DDD)中,**Entities(实体)** 和 **Value Objects(值对象)** 是领域层的核心概念,用于建模业务领域中的对象
实体是领域模型中具有唯一标识和生命周期的对象,代表业务领域中的某个具体实例。实体的核心特性是其身份(Identity),即使属性发生变化,实体仍然保持其唯一性。值对象是领域模型中没有唯一标识的对象,通过其属性值来定义其身份。值对象通常表示不可变的、描述性的概念,强调值的相等性而非实例的唯一性。Entities:具有唯一标识和生命周期,适合表示业务对象(如:无唯一标识,强调值相等性,适合描述数据(如
在领域驱动设计(Domain-Driven Design, DDD)中,Entities(实体) 和 Value Objects(值对象) 是领域层的核心概念,用于建模业务领域中的对象。它们在设计和实现上有着本质的区别,理解两者的差异对于构建清晰、可维护的领域模型至关重要,尤其是在像 WpfPowerTester 这样的复杂测试系统中,合理使用 Entities 和 Value Objects 可以提高代码的可读性和可扩展性。以下是关于 Entities 和 Value Objects 的详细讲解,包括定义、特点、区别、应用场景以及在 WpfPowerTester 框架中的示例。
一、Entities(实体)的定义与特点
1. 定义
实体是领域模型中具有唯一标识和生命周期的对象,代表业务领域中的某个具体实例。实体的核心特性是其身份(Identity),即使属性发生变化,实体仍然保持其唯一性。
2. 特点
- 唯一标识:
- 每个实体都有一个唯一的标识符(通常是一个 ID,如
Guid或整数),用于区分不同的实例。 - 示例:在 WpfPowerTester 中,
ProjectModel是一个实体,每个工程通过Guid唯一标识。
- 每个实体都有一个唯一的标识符(通常是一个 ID,如
- 可变性(Mutability):
- 实体的属性可以在其生命周期内发生变化,但其身份保持不变。
- 示例:
ProjectModel的ProjectName或ProjectRunStatus可以修改,但其Guid不变。
- 生命周期:
- 实体具有明确的生命周期(如创建、运行、完成),可能涉及状态转换。
- 示例:
ProjectModel的状态从Idle到Running再到Completed。
- 行为:
- 实体通常包含业务逻辑和行为,封装与自身相关的操作。
- 示例:
ProjectModel的RunCommand触发测试流程。
- 持久化:
- 实体通常需要持久化到数据库或其他存储中,保持其状态。
- 示例:
ProjectModel的数据(如ProjectName、ParamSetupModel)存储在Global.ProjectList或数据库中。
3. 示例代码
在 WpfPowerTester 中,ProjectModel 是一个典型的实体:
public class ProjectModel : BindableBase, IDisposable
{
private Guid _guid;
private string _projectName;
private int _projectRunStatus;
public Guid Guid
{
get => _guid;
set => SetProperty(ref _guid, value);
}
public string ProjectName
{
get => _projectName;
set => SetProperty(ref _projectName, value);
}
public int ProjectRunStatus
{
get => _projectRunStatus;
set => SetProperty(ref _projectRunStatus, value);
}
public DelegateCommand<object> RunCommand { get; }
public ProjectModel(HardwareDriverManager driverManager, ChartService chartService, ZoneManager zoneManager)
{
Guid = Guid.NewGuid();
RunCommand = new DelegateCommand<object>(async (obj) => await DoRunCommandAsync(obj));
}
}
- 唯一标识:
Guid确保每个ProjectModel实例唯一。 - 可变性:
ProjectName和ProjectRunStatus可更改。 - 行为:
RunCommand封装测试逻辑。 - 生命周期:从创建到运行、完成,状态通过
ProjectRunStatus管理。
二、Value Objects(值对象)的定义与特点
1. 定义
值对象是领域模型中没有唯一标识的对象,通过其属性值来定义其身份。值对象通常表示不可变的、描述性的概念,强调值的相等性而非实例的唯一性。
2. 特点
- 无唯一标识:
- 值对象不依赖 ID,其身份由属性值的组合决定。
- 示例:在 WpfPowerTester 中,
ParamSetupModel可以作为值对象,表示测试参数配置。
- 不可变性(Immutability):
- 值对象的属性通常是只读的,创建后不可修改;如需修改,创建新实例。
- 示例:
ParamSetupModel的属性(如ProjectMethod)在设置后不直接修改,而是生成新对象。
- 值相等性:
- 两个值对象如果所有属性值相同,则认为它们相等。
- 示例:两个
ParamSetupModel实例如果ProjectMethod和其他参数相同,则视为同一配置。
- 无生命周期:
- 值对象没有独立的生命周期,通常依附于实体。
- 示例:
ParamSetupModel作为ProjectModel的属性存在。
- 轻量级:
- 值对象通常较小,专注于描述数据而非行为。
- 示例:
InputParamModel存储参数名、值和单位(如{ ParamName = "Voltage", ParamValue = "5", ParamUnit = "V" })。
- 可共享:
- 值对象可以在多个实体间共享,减少内存占用。
- 示例:多个
ProjectModel可以共享相同的ParamSetupModel实例(如果不可变)。
3. 示例代码
在 WpfPowerTester 中,ParamSetupModel 和 InputParamModel 是值对象:
public class ParamSetupModel
{
public string ProjectMethod { get; init; }
public string ProjectMode { get; init; }
public bool ProjectModeVisible { get; init; }
public bool IsOpenBigFlow { get; init; }
public bool ProjectIGesMeas { get; init; }
public bool ProjectZthMeas { get; init; }
public bool ProjectTcMeas { get; init; }
public ParamSetupModel(string projectMethod, string projectMode = null)
{
ProjectMethod = projectMethod;
ProjectMode = projectMode;
ProjectModeVisible = !string.IsNullOrEmpty(projectMode);
}
public override bool Equals(object obj)
{
if (obj is ParamSetupModel other)
{
return ProjectMethod == other.ProjectMethod &&
ProjectMode == other.ProjectMode &&
ProjectModeVisible == other.ProjectModeVisible &&
IsOpenBigFlow == other.IsOpenBigFlow &&
ProjectIGesMeas == other.ProjectIGesMeas &&
ProjectZthMeas == other.ProjectZthMeas &&
ProjectTcMeas == other.ProjectTcMeas;
}
return false;
}
public override int GetHashCode()
{
return HashCode.Combine(ProjectMethod, ProjectMode, ProjectModeVisible, IsOpenBigFlow, ProjectIGesMeas, ProjectZthMeas, ProjectTcMeas);
}
}
public class InputParamModel
{
public string ParamName { get; init; }
public string ParamValue { get; init; }
public string ParamUnit { get; init; }
public string IsVisible { get; init; }
public InputParamModel(string paramName, string paramValue, string paramUnit, string isVisible = "Visible")
{
ParamName = paramName;
ParamValue = paramValue;
ParamUnit = paramUnit;
IsVisible = isVisible;
}
public override bool Equals(object obj)
{
if (obj is InputParamModel other)
{
return ParamName == other.ParamName &&
ParamValue == other.ParamValue &&
ParamUnit == other.ParamUnit &&
IsVisible == other.IsVisible;
}
return false;
}
public override int GetHashCode()
{
return HashCode.Combine(ParamName, ParamValue, ParamUnit, IsVisible);
}
}
- 无唯一标识:
ParamSetupModel和InputParamModel没有 ID,身份由属性值决定。 - 不可变性:使用
init确保属性在构造后不可修改。 - 值相等性:重写
Equals和GetHashCode以比较属性值。 - 依附实体:
ParamSetupModel和InputParamModel作为ProjectModel的属性存在。
三、Entities 与 Value Objects 的区别
以下是 Entities 和 Value Objects 的详细对比:
| 特性 | Entities(实体) | Value Objects(值对象) |
|---|---|---|
| 身份 | 有唯一标识(如 Guid) |
无唯一标识,身份由属性值决定 |
| 可变性 | 可变,属性可随生命周期变化 | 通常不可变,修改需创建新实例 |
| 生命周期 | 有明确的生命周期(如创建、运行、完成) | 无独立生命周期,依附于实体 |
| 行为 | 包含业务逻辑和行为 | 通常只包含数据,行为较少 |
| 相等性 | 通过唯一标识比较(即使属性相同) | 通过属性值比较(属性相同即相等) |
| 持久化 | 通常需要持久化到数据库 | 通常作为实体的部分持久化 |
| 示例 | ProjectModel(工程) |
ParamSetupModel(测试参数)、InputParamModel(输入参数) |
1. 身份与相等性
- 实体:即使两个实体的属性完全相同,只要
Id不同,它们就是不同的实体。- 示例:两个
ProjectModel实例即使ProjectName相同,只要Guid不同,它们是不同工程。
- 示例:两个
- 值对象:两个值对象如果所有属性值相同,则认为是同一个值对象。
- 示例:两个
ParamSetupModel实例如果ProjectMethod和其他属性相同,则视为相同配置。
- 示例:两个
2. 可变性
- 实体:属性可变,适合表示状态变化的对象。
- 示例:
ProjectModel的ProjectRunStatus从Idle变为Running。
- 示例:
- 值对象:不可变,确保数据一致性,修改时创建新实例。
- 示例:修改
ParamSetupModel.ProjectMethod时,创建新实例:var oldConfig = project.ParamSetupModel; project.ParamSetupModel = new ParamSetupModel("NewMethod", oldConfig.ProjectMode);
- 示例:修改
3. 生命周期
- 实体:有明确的创建、更新、删除流程。
- 示例:
ProjectModel从创建(AddProjectCommand)到运行(RunCommand)到删除(DeleteProjectCommand)。
- 示例:
- 值对象:无独立生命周期,依附于实体。
- 示例:
InputParamModel随ProjectModel的创建或删除而存在。
- 示例:
4. 行为与职责
- 实体:封装业务逻辑,如硬件交互、测试执行。
- 示例:
ProjectModel的DoRunCommandAsync调用测试流程。
- 示例:
- 值对象:主要描述数据,行为较少,通常提供简单计算或验证。
- 示例:
ParamSetupModel提供ProjectMethod的验证逻辑。
- 示例:
四、在 WpfPowerTester 中的应用场景
结合 WpfPowerTester 框架,Entities 和 Value Objects 的应用如下:
1. Entities 的应用
- 场景:表示具有唯一身份和生命周期的业务对象,如工程(
ProjectModel)、工作站(WorkstationModel)。 - 示例:
ProjectModel:每个工程有唯一Guid,支持创建、运行、删除等操作。WorkstationModel:表示测试工位,可能有唯一 ID 和状态(如占用、闲置)。
- 代码:
public class ProjectModel : BindableBase { public Guid Guid { get; set; } public string ProjectName { get; set; } public int ProjectRunStatus { get; set; } public async Task RunAsync() { ProjectRunStatus = (int)ProjectStatus.Running; // 执行测试流程 } }
2. Value Objects 的应用
- 场景:表示描述性、无唯一身份的数据,如测试参数(
ParamSetupModel)、输入参数(InputParamModel)、通道配置(ChannelModel的子集)。 - 示例:
ParamSetupModel:表示测试方法和模式配置,属性不可变。InputParamModel:表示单个参数(如电压、电流),通过值比较相等性。
- 代码:
public class ParamSetupModel { public string ProjectMethod { get; init; } public bool Equals(ParamSetupModel other) => ProjectMethod == other.ProjectMethod; } public class InputParamModel { public string ParamName { get; init; } public string ParamValue { get; init; } public string ParamUnit { get; init; } public bool Equals(InputParamModel other) => ParamName == other.ParamName && ParamValue == other.ParamValue; }
3. 结合使用
- 场景:
ProjectModel(实体)包含ParamSetupModel和ObservableCollection<InputParamModel>(值对象)。 - 代码:
public class ProjectModel : BindableBase { public Guid Guid { get; set; } public ParamSetupModel ParamSetupModel { get; set; } // 值对象 public ObservableCollection<InputParamModel> ParamList { get; } // 值对象集合 }- 实体:
ProjectModel通过Guid标识唯一工程。 - 值对象:
ParamSetupModel和ParamList描述工程的配置和参数,无独立身份。
- 实体:
五、设计注意事项
1. Entities 的设计
- 唯一标识:始终为实体定义唯一 ID,避免依赖属性比较。
- 示例:使用
Guid而非ProjectName作为ProjectModel的标识。
- 示例:使用
- 行为封装:将业务逻辑放在实体中,避免分散到 ViewModel。
- 示例:
ProjectModel的测试逻辑在RunCommand中实现。
- 示例:
- 状态管理:使用枚举(如
ProjectStatus)管理实体状态。- 示例:
ProjectRunStatus枚举值(Idle、Running、Completed)。
- 示例:
2. Value Objects 的设计
- 不可变性:使用
init或只读属性确保值对象不可变。- 示例:
ParamSetupModel的ProjectMethod使用init。
- 示例:
- 相等性比较:重写
Equals和GetHashCode。- 示例:
InputParamModel比较ParamName、ParamValue等。
- 示例:
- 轻量级:避免在值对象中添加复杂行为。
- 示例:
InputParamModel只存储数据,不包含测试逻辑。
- 示例:
3. Entities 与 Value Objects 的交互
- 组合:实体包含值对象,值对象为实体提供描述性数据。
- 示例:
ProjectModel包含ParamSetupModel和ParamList。
- 示例:
- 替换:修改值对象时,创建新实例并替换。
- 示例:
project.ParamSetupModel = new ParamSetupModel("NewMethod", project.ParamSetupModel.ProjectMode);
- 示例:
- 共享:值对象可在多个实体间共享,减少内存占用。
- 示例:多个
ProjectModel共享相同的ParamSetupModel实例(需确保不可变)。
- 示例:多个
六、在 WpfPowerTester 中的优化建议
结合 WpfPowerTester 框架,以下是 Entities 和 Value Objects 的优化建议:
1. 实体优化
- 持久化:
- 将
ProjectModel持久化到数据库(如 SQLite),保存Guid和属性。public class ProjectRepository { public void Save(ProjectModel project) { // 保存到数据库 } }
- 将
- 状态机:
- 使用状态机管理
ProjectRunStatus,确保状态转换合法。public class ProjectModel : BindableBase { public void TransitionToRunning() { if (ProjectRunStatus != (int)ProjectStatus.Idle) throw new InvalidOperationException("只能从空闲状态进入运行状态"); ProjectRunStatus = (int)ProjectStatus.Running; } }
- 使用状态机管理
2. 值对象优化
- 不可变性:
- 确保
ParamSetupModel和InputParamModel不可变,使用record类型简化实现:public record ParamSetupModel(string ProjectMethod, string ProjectMode, bool ProjectModeVisible, bool IsOpenBigFlow);
- 确保
- 验证逻辑:
- 在值对象中添加验证方法:
public record InputParamModel(string ParamName, string ParamValue, string ParamUnit) { public bool IsValid() => !string.IsNullOrEmpty(ParamName) && double.TryParse(ParamValue, out _); }
- 在值对象中添加验证方法:
3. 性能优化
- 值对象缓存:
- 使用缓存避免重复创建值对象:
private static readonly Dictionary<string, ParamSetupModel> _paramSetupCache = new(); public static ParamSetupModel CreateParamSetup(string method) { return _paramSetupCache.GetOrAdd(method, m => new ParamSetupModel(m)); }
- 使用缓存避免重复创建值对象:
- 实体筛选:
- 优化
ProjectViewModel的筛选逻辑,减少实体到值对象的转换开销:private void UpdateFilteredProjects() { var filtered = Global.ProjectList .Where(p => string.IsNullOrEmpty(SearchText) || p.ProjectName.Contains(SearchText)) .Select(p => new ProjectDisplayModel { Guid = p.Guid, ProjectName = p.ProjectName }) .ToList(); DisplayProjects.Clear(); foreach (var item in filtered) DisplayProjects.Add(item); }
- 优化
4. 可扩展性
- 动态值对象:
- 支持动态加载参数配置:
public class ParamSetupModel { public static ParamSetupModel FromConfig(string configFile) { var json = File.ReadAllText(configFile); return JsonSerializer.Deserialize<ParamSetupModel>(json); } }
- 支持动态加载参数配置:
- 实体扩展:
- 为
ProjectModel添加扩展点,支持新测试方法:public interface IProjectExtension { Task ExecuteAsync(ProjectModel project); }
- 为
七、总结
1. Entities vs. Value Objects
- Entities:具有唯一标识和生命周期,适合表示业务对象(如
ProjectModel)。 - Value Objects:无唯一标识,强调值相等性,适合描述数据(如
ParamSetupModel、InputParamModel)。 - 区别:身份、可变性、生命周期、行为和持久化方式。
2. 在 WpfPowerTester 中的应用
- Entities:
ProjectModel表示工程,管理测试流程和状态。 - Value Objects:
ParamSetupModel和InputParamModel描述测试参数。 - 交互:实体包含值对象,值对象提供描述性数据。
3. 优化建议
- 实体:持久化、状态机管理。
- 值对象:不可变性、验证逻辑、缓存。
- 性能:缓存值对象、优化筛选。
- 可扩展性:动态配置、扩展点。
通过合理设计 Entities 和 Value Objects,WpfPowerTester 的领域层可以实现清晰的业务建模,满足复杂测试系统的需求,同时保持代码的可维护性和可扩展性。
更多推荐




所有评论(0)