在领域驱动设计(Domain-Driven Design, DDD)中,Entities(实体)Value Objects(值对象) 是领域层的核心概念,用于建模业务领域中的对象。它们在设计和实现上有着本质的区别,理解两者的差异对于构建清晰、可维护的领域模型至关重要,尤其是在像 WpfPowerTester 这样的复杂测试系统中,合理使用 Entities 和 Value Objects 可以提高代码的可读性和可扩展性。以下是关于 Entities 和 Value Objects 的详细讲解,包括定义、特点、区别、应用场景以及在 WpfPowerTester 框架中的示例。


一、Entities(实体)的定义与特点

1. 定义

实体是领域模型中具有唯一标识生命周期的对象,代表业务领域中的某个具体实例。实体的核心特性是其身份(Identity),即使属性发生变化,实体仍然保持其唯一性。

2. 特点
  • 唯一标识
    • 每个实体都有一个唯一的标识符(通常是一个 ID,如 Guid 或整数),用于区分不同的实例。
    • 示例:在 WpfPowerTester 中,ProjectModel 是一个实体,每个工程通过 Guid 唯一标识。
  • 可变性(Mutability)
    • 实体的属性可以在其生命周期内发生变化,但其身份保持不变。
    • 示例:ProjectModelProjectNameProjectRunStatus 可以修改,但其 Guid 不变。
  • 生命周期
    • 实体具有明确的生命周期(如创建、运行、完成),可能涉及状态转换。
    • 示例:ProjectModel 的状态从 IdleRunning 再到 Completed
  • 行为
    • 实体通常包含业务逻辑和行为,封装与自身相关的操作。
    • 示例:ProjectModelRunCommand 触发测试流程。
  • 持久化
    • 实体通常需要持久化到数据库或其他存储中,保持其状态。
    • 示例:ProjectModel 的数据(如 ProjectNameParamSetupModel)存储在 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 实例唯一。
  • 可变性ProjectNameProjectRunStatus 可更改。
  • 行为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 中,ParamSetupModelInputParamModel 是值对象:

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);
    }
}
  • 无唯一标识ParamSetupModelInputParamModel 没有 ID,身份由属性值决定。
  • 不可变性:使用 init 确保属性在构造后不可修改。
  • 值相等性:重写 EqualsGetHashCode 以比较属性值。
  • 依附实体ParamSetupModelInputParamModel 作为 ProjectModel 的属性存在。

三、Entities 与 Value Objects 的区别

以下是 Entities 和 Value Objects 的详细对比:

特性 Entities(实体) Value Objects(值对象)
身份 有唯一标识(如 Guid 无唯一标识,身份由属性值决定
可变性 可变,属性可随生命周期变化 通常不可变,修改需创建新实例
生命周期 有明确的生命周期(如创建、运行、完成) 无独立生命周期,依附于实体
行为 包含业务逻辑和行为 通常只包含数据,行为较少
相等性 通过唯一标识比较(即使属性相同) 通过属性值比较(属性相同即相等)
持久化 通常需要持久化到数据库 通常作为实体的部分持久化
示例 ProjectModel(工程) ParamSetupModel(测试参数)、InputParamModel(输入参数)
1. 身份与相等性
  • 实体:即使两个实体的属性完全相同,只要 Id 不同,它们就是不同的实体。
    • 示例:两个 ProjectModel 实例即使 ProjectName 相同,只要 Guid 不同,它们是不同工程。
  • 值对象:两个值对象如果所有属性值相同,则认为是同一个值对象。
    • 示例:两个 ParamSetupModel 实例如果 ProjectMethod 和其他属性相同,则视为相同配置。
2. 可变性
  • 实体:属性可变,适合表示状态变化的对象。
    • 示例:ProjectModelProjectRunStatusIdle 变为 Running
  • 值对象:不可变,确保数据一致性,修改时创建新实例。
    • 示例:修改 ParamSetupModel.ProjectMethod 时,创建新实例:
      var oldConfig = project.ParamSetupModel;
      project.ParamSetupModel = new ParamSetupModel("NewMethod", oldConfig.ProjectMode);
      
3. 生命周期
  • 实体:有明确的创建、更新、删除流程。
    • 示例:ProjectModel 从创建(AddProjectCommand)到运行(RunCommand)到删除(DeleteProjectCommand)。
  • 值对象:无独立生命周期,依附于实体。
    • 示例:InputParamModelProjectModel 的创建或删除而存在。
4. 行为与职责
  • 实体:封装业务逻辑,如硬件交互、测试执行。
    • 示例:ProjectModelDoRunCommandAsync 调用测试流程。
  • 值对象:主要描述数据,行为较少,通常提供简单计算或验证。
    • 示例: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(实体)包含 ParamSetupModelObservableCollection<InputParamModel>(值对象)。
  • 代码
    public class ProjectModel : BindableBase
    {
        public Guid Guid { get; set; }
        public ParamSetupModel ParamSetupModel { get; set; } // 值对象
        public ObservableCollection<InputParamModel> ParamList { get; } // 值对象集合
    }
    
    • 实体ProjectModel 通过 Guid 标识唯一工程。
    • 值对象ParamSetupModelParamList 描述工程的配置和参数,无独立身份。

五、设计注意事项

1. Entities 的设计
  • 唯一标识:始终为实体定义唯一 ID,避免依赖属性比较。
    • 示例:使用 Guid 而非 ProjectName 作为 ProjectModel 的标识。
  • 行为封装:将业务逻辑放在实体中,避免分散到 ViewModel。
    • 示例:ProjectModel 的测试逻辑在 RunCommand 中实现。
  • 状态管理:使用枚举(如 ProjectStatus)管理实体状态。
    • 示例:ProjectRunStatus 枚举值(IdleRunningCompleted)。
2. Value Objects 的设计
  • 不可变性:使用 init 或只读属性确保值对象不可变。
    • 示例:ParamSetupModelProjectMethod 使用 init
  • 相等性比较:重写 EqualsGetHashCode
    • 示例:InputParamModel 比较 ParamNameParamValue 等。
  • 轻量级:避免在值对象中添加复杂行为。
    • 示例:InputParamModel 只存储数据,不包含测试逻辑。
3. Entities 与 Value Objects 的交互
  • 组合:实体包含值对象,值对象为实体提供描述性数据。
    • 示例:ProjectModel 包含 ParamSetupModelParamList
  • 替换:修改值对象时,创建新实例并替换。
    • 示例:
      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. 值对象优化
  • 不可变性
    • 确保 ParamSetupModelInputParamModel 不可变,使用 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:无唯一标识,强调值相等性,适合描述数据(如 ParamSetupModelInputParamModel)。
  • 区别:身份、可变性、生命周期、行为和持久化方式。
2. 在 WpfPowerTester 中的应用
  • EntitiesProjectModel 表示工程,管理测试流程和状态。
  • Value ObjectsParamSetupModelInputParamModel 描述测试参数。
  • 交互:实体包含值对象,值对象提供描述性数据。
3. 优化建议
  • 实体:持久化、状态机管理。
  • 值对象:不可变性、验证逻辑、缓存。
  • 性能:缓存值对象、优化筛选。
  • 可扩展性:动态配置、扩展点。

通过合理设计 Entities 和 Value Objects,WpfPowerTester 的领域层可以实现清晰的业务建模,满足复杂测试系统的需求,同时保持代码的可维护性和可扩展性。

Logo

苏州本地的技术开发者社区,在这里可以交流本地的好吃好玩的,可以交流技术,可以交流招聘等等,没啥限制。

更多推荐