DDD:类似枚举的实体

17

我有以下的数据库模型:

**Person table**
ID    |    Name    | StateId
------------------------------
1          Joe       1
2          Peter     1
3          John      2

**State table**
ID    |    Desc
------------------------------
1          Working
2          Vacation

领域模型将会是(简化版):

public class Person
{
    public int Id { get; }
    public string Name { get; set; }
    public State State { get; set; }
}

public class State
{
    private int id;
    public string Name { get; set; }
}

这里的状态可能会在领域逻辑中使用,例如:

if(person.State == State.Working)
    // some logic

据我理解,状态(State)类似于值对象,用于域逻辑检查。但它还需要在数据库模型中存在,以表示一个清晰的实体关系映射。

因此状态可能会被扩展为:

public class State
{
    private int id;
    public string Name { get; set; }

    public static State New {get {return new State([hardCodedIdHere?], [hardCodeNameHere?]);}}
}

然而,使用这种方法状态名称将被硬编码到域名中。

你知道我的意思吗?是否有标准的方法来处理这种情况?从我的角度来看,我试图在我的领域中使用一个对象(从ERM设计的角度来看是持久化的),作为一种值对象。你怎么想?

问题更新: 可能我的问题表述不太清楚。

我需要知道如何在我的领域逻辑中使用存储在数据库中的实体(例如"State"示例)。以避免出现类似以下代码的情况:

  if(person.State.Id == State.Working.Id)
      // some logic
或者
if(person.State.Id == WORKING_ID)
// some logic

由于您的状态在数据库中有一个ID,据我所知它们是实体(值对象是通过其属性而不是ID进行标识的)。也许您应该放弃它们并直接将值存储在相应的数据库表中? - ZeissS
6个回答

12
您提出的结构看起来不错。(术语分歧:由于State有一个ID,它不是一个Value Object,而是一个Entity。)
枚举是一种代码异味,因此不要尝试使用该方法。使用状态(State)模式将行为移入State对象中更符合面向对象的思想。
与其编写以下代码
if (person.State == State.Working)
    // do something...

在你的代码中随处可见这个,这样可以让你更容易地编写

person.State.DoSomething();

这样更加简洁,而且如果需要可以添加新的状态。


1
这个问题与如何实现状态模式有关,假设提问者正在使用状态模式。 - Brian Leahy
2
@Brian Leahy:我在问题中没有看到任何关于状态模式的提及... - Mark Seemann
1
他正在使用从数据库值中恢复的状态对象。他正在映射状态工作和状态休假。你的回答有点解释了状态模式,但没有解释如何进行恢复...他的问题涉及到恢复而不是模式本身的使用。 - Brian Leahy

10

一直在使用他的枚举类...偶然发现了一个升级版。https://github.com/HeadspringLabs/Enumeration - CSharper
还可以作为NuGet包使用!不错... http://www.nuget.org/packages/Enumeration - rohancragg
2
我仍然不明白如何将枚举与数据库中的记录同步,特别是ID值,如果您持久化枚举。 - David Liang

4

在枚举中,通常会包含一个值为0的“未知”元素。如果您真的想要这样做并将其用于New状态,则可以这样做。

但是,您所描述的是业务逻辑...在创建新对象后设置状态应该在业务逻辑层中进行,而不是在类本身内部进行。


2

我个人认为针对ID编程是一个错误。相反,我会修改您的表格如下:

   **State table**
ID    |    Desc               | IsWorking  | IsVacation
-----------------------------------------------------------
1          Working                True         False
2          Vacation               False        True

接下来,我将使用这些属性做出商业决策,例如:

    public void MakeDecisionOnState(State state)
    {
        if (state.IsVacation)
            DoSomething();
        if (state.IsWorking)
            DoSomethingElse();
    }

或者更聪明的做法是,使用工厂模式根据这些属性创建正确的实例:

    public abstract class State
    {
        public Guid Id { get; set; }

        public string Description { get; set; }

        public abstract void DoSomething();

    }

    public class WorkingState : State
    {
        public override void DoSomething()
        {
            //Do something specific for the working state
        }
    }

    public class VacationState : State
    {
        public override void DoSomething()
        {
            //Do something specific for the vacation state
        } 
    }

    public class StateFactory
    {
        public static State CreateState(IDataRecord record)
        {
            if (record.GetBoolean(2))
                return new WorkingState { Id = record.GetGuid(0), Description = record.GetString(1) };
            if (record.GetBoolean(3))
                return new VacationState { Id = record.GetGuid(0), Description = record.GetString(1) };

            throw new Exception("Data is screwed");
        }
    }

现在你已经消除了if/switch语句,你的代码可以简单地写成:
state.DoSomething();

我这样做的原因是,通常这些实体可以由客户进行配置,即他们可能不希望在系统中激活某些状态,或者他们可能希望将它们称为其他名称。通过针对属性进行编程,客户可以随意删除/编辑记录,即使该过程生成新的ID,也不会影响系统,他们只需要设置属性即可。


2
你需要创建一个工厂方法,根据存储的值来实例化所需的适当状态类。就像这样:
public  static State GetStateByID( StateEnum value)
{
   if(value.Invalid)
        throw new Exception();

switch(value)
   case State.Working
        return new WorkingState();
   case State.somethingelse
         return new somethingelseState();
   case State.something
         return new somethingState();
   case State.whatever
         return new whateverState();

}

在使用枚举时,总是尝试将0作为无效值。在幕后,枚举是值类型,未分配的int始终为0。

通常与状态模式一起使用工厂,例如此代码。

因此,当从数据库读取存储的整数值时,您可以将int强制转换为枚举,并调用工厂以获取适当的状态对象。


1
在我看来,域层必须与数据库模型/ERM设计分离。我对于你最终提出的State类建议有些困惑。在我看来,这不利于建立共同语言,而这是DDD的主要目的之一。
我会选择更简单的设计。状态应该属于Person类,我会将其包含在类中。
public class Person
{
    public int Id { get; }
    public string Name { get; set; }
    public PersonState State { get; set; }
}

状态本身似乎具有已定义的值(我假设在您的上下文中,“人”是一名员工),这些值不会经常更改。因此,我会将其建模为枚举并将其视为数据类型。

enum Days {Working, Vacation};

在我看来,这是一个易于理解的简单设计。将映射到ERM设计的工作应该在持久层中完成。在那里,枚举必须被映射到状态表的键。可以使用方面来保持原始域模型的清晰。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接