在领域驱动设计(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)