【问题标题】:Composition over Inheritance - where do extra properties go?组合优于继承——额外的属性去哪儿了?
【发布时间】:2011-10-04 15:51:35
【问题描述】:

从示例 HR 系统中获取以下代码。用户有能力记录缺勤,并且可以是各种类型,包括假期和疾病。这将是一个基于 ORM(如 NHibernate)的域模型。

public class Absence
{
    public long Id {get;set;}
    public Employee Employee {get;set;}
    public DateTime StartDate {get;set;}        
    public DateTime EndDate {get;set;}

    public virtual void DoSomething()
    { ... }
}

public class Holiday : Absence
{ 
    public string Location {get;set;}

    public override void DoSomething()
    { ... }
}

public class Sickness : Absence
{
    public bool DoctorsNoteProvided {get;set;}

    public override void DoSomething()
    { ... }
}

这是一个示例 - 请不要质疑为什么需要位置,假设它是一个规范。

用户想要更改类型 - 他以为员工生病了,但后来想起那是假期。同样,您可能认为这是一个糟糕的设计,但将其视为一项需求——这代表了一个对我来说已经多次出现的问题。

问题是您无法将对象的类型从 Sickness 更改为 Absence。一般来说,建议是优先组合而不是继承(四人组)并这样做:

public class Absence
{
    public long Id {get;set;}
    public Employee Employee {get;set;}
    public DateTime StartDate {get;set;}        
    public DateTime EndDate {get;set;}

    public AbsenceType Type {get;set;}

    public void DoSomething()
    {
        Type.DoSomething();
    }
}

但是当我这样做时,Holiday 和 Sickness 的特定属性何时会消失(分别是 Location 和 DoctorsNoteProvided)?

【问题讨论】:

    标签: nhibernate design-patterns architecture domain-driven-design


    【解决方案1】:

    我将通过为 AbsenceType 或 AbsenseReason 设置类型层次结构来对此进行建模:

    abstract class AbsenseReason {
    }
    
    class HolidayAbsenseReason : AbsenseReason {
        public string Name { get; }
    }
    

    我喜欢这个模型,因为现在 AbsenseReason 是一个值对象,并且独立于一个实体对象的员工缺席。正如您所说,这解决了更改缺勤原因的问题。一般来说,我会赞成这样做而不是删除记录,因为可能还有许多关联需要考虑。

    需要考虑的事项:

    • NHibernate 不支持组件上的继承映射,因此您必须提供 IUserType 的自定义实现。
    • 考虑将不同缺勤原因子类型的所有数据与员工缺勤实体的记录一起存储。可能是 XML,这样您就可以拥有集合等。

    【讨论】:

    • 我确实设想会有 SicknessAbsenceType、HolidayAbsenceType 继承自 AbsenceType。我可能应该在我的帖子中更好地解释这一点。但是,我将它视为一个实体,而不是一个值。我不确定您如何建议我将特定于缺勤的信息存储在实际缺勤类型中?在我的概念中,它通常只包含有关假期/疾病的信息,而不是关于一个特定实例的信息。我有兴趣了解您的理论吗?
    【解决方案2】:

    所以尝试将所有类型特定的功能移至AbsenceType 派生类。如果他们需要来自父类Absence 的东西,您可以将其参考传递给他们。虽然我会尽量避免这种情况。

    如果您通过基类接口操作Absence 对象,则没有任何变化,您可以保留旧代码。现在,如果你操作了特定的衍生品,那么你将不得不从特定的 Absence 中获取 AbsenceType 对象并对它们执行所有相同的操作 - 仍然没有太大的改变。如果你有holiday.DoSomething(),现在你有holiday.Type.DoSomething()

    【讨论】:

    • 我不确定您建议我将假期/疾病特定属性存储在哪里?
    • 是的,AbsenceType 将是一种值类型,没有标识,将仅属于单个 Absence 对象,并且所有类型特定的行为都应移至其派生类:SicknessAbsenceType , HolidayAbsenceType, ...
    • 这是对 eulerfx 的类似回答。这似乎是人们使用的一种我并不完全熟悉的模式。我还是不太明白。请给我一个代码示例好吗?
    【解决方案3】:

    这不是组合优于继承的正确位置。这里继承是合适的。如果您需要更改缺勤类型,只需创建一个新的并删除旧的。

    【讨论】:

      【解决方案4】:

      为什么需要更改对象的类型?

      你会有一些缺席的集合,只需替换有问题的项目。

      可以想象,与其替换您,甚至保留原始请求并将其标记为已取代,这对于审计跟踪目的可能很重要。

      【讨论】:

      • 三个答案都说同样的事情,很难在它们之间做出选择,但这个答案刚刚好,因为它考虑了审计跟踪等。Mike Mozhaev 和 Ray Toal 得到了支持,但这得到了成功!
      【解决方案5】:

      嗯,在不了解您的要求的情况下,我想说正确的设计不是将 Absence 对象更改为 Sickness 对象(反之亦然),而只是删除您不想要的对象并创建一个新对象你做的类型。您必须在某个地方维护缺勤集合,对吧?

      你说得对,课程不会改变。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-12-13
        • 1970-01-01
        • 2018-06-30
        • 2011-08-02
        • 1970-01-01
        • 2013-01-03
        相关资源
        最近更新 更多