组合优于继承——额外的属性去哪里了?
从示例 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()
{ ... }
}
这是一个示例 - 请不要质疑为什么需要位置,假设它是一个规范。
用户想要更改类型 - 他以为员工请病假,但后来想起这是假期。同样,您可能认为这是一个糟糕的设计,但将其视为一项要求 - 这代表了我多次遇到的问题。
问题是您无法将对象的类型从“生病”更改为“缺席”。一般来说,建议是优先考虑组合而非继承(四人组)并执行以下操作:
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();
}
}
但是当我执行此操作时,特定于假期和疾病的属性何时消失(分别为 Location 和 DoctorsNoteProvided)?
Take this following code from an example HR system. The user has the ability to log an absence and can be of various types including holiday and sickness. This would be a domain model over an ORM such as 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()
{ ... }
}
This is an example - please don't question why location would be required, assume it is a specification.
The user wants to change the type - he thought the employee was off sick but then remembered it was a holiday. Again, you may think this is a bad design but treat it like a requirement - this represents a problem that has come up many times for me.
The problem is that you cannot change the type of an object from Sickness to Absence. Generally, the advice would be to Favour Composition Over Inheritance (Gang of Four) and do this:
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();
}
}
But when I do this, when do the properties specific to Holiday and Sickness go (Location and DoctorsNoteProvided respectively)?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
为什么需要改变对象的类型?
您将拥有某种缺勤集合,只需替换有问题的项目即可。
可以想象,您甚至可以保留原始请求并将其标记为已取代,而不是替换您,这对于审计跟踪目的可能很重要。
Why do you need to change the type of an object?
You will have some kind of collection of Absences, just replace the item in question.
Conceivably rather than replacing you even keep the original request and mark it as superceded, that might be important for audit trail purposes.
这不是组合优于继承的正确位置。这里继承是合适的。如果您需要更改缺勤类型,只需创建一个新缺勤类型并删除旧缺勤类型即可。
It's not the right place for Composition over Inheritance. Here the inheritance is appropriate. And if you need to change the type of absence just create a new one and delete old.
嗯,在不了解更多关于您的要求的情况下,我想说正确的设计不是将缺勤对象更改为疾病对象(反之亦然),而是删除您不想要的对象并创建一个新的类型你做。您一定在某个地方维护着缺勤集合,对吧?
你是对的,班级不会改变。
Hmmm, without knowing more about your requirements, I would say the right design is not to change an Absence object to a Sickness object (or vice versa) but to just delete the one you don't want and create a new one of the type you do. Somewhere you must be maintaining a collection of absences, right?
You are correct that classes don't change.
我将通过为 AbsenceType 或 AbsenseReason 建立一个类型层次结构来对此进行建模:
我喜欢这个模型,因为现在 AbsenseReason 是一个值对象,并且独立于员工 Absence,而员工 Absence 是一个实体对象。正如您所说,这解决了更改缺勤原因的问题。一般来说,我更喜欢这样做而不是删除记录,因为可能还有很多关联需要考虑。
需要考虑的事项:
I would model this by having a type hierarchy for an AbsenceType, or AbsenseReason:
I like this model because now AbsenseReason is a value object and is independent of an employee Absence, which is an entity object. This, as you stated, solves the issue with changing the absence reason. Generally speaking, I would favor this over deleting a record, because there may be many associations to consider as well.
Things to consider:
因此,请尝试将所有类型特定功能移至
AbsenceType
派生类。如果他们需要父类Absence
中的某些内容,您可以将其引用传递给他们。尽管我会尽力避免这种情况。如果您通过基类接口操作
Absence
对象,则不会发生任何变化,您可以保留旧代码。现在,如果您操纵特定的导数,那么您将必须从特定的Absence
中获取AbsenceType
对象,并对它们执行所有相同的操作 - 仍然没有太多改变。如果您有holiday.DoSomething()
,现在您就有holiday.Type.DoSomething()
。So try to move all type specific functionality to
AbsenceType
derivatives. If they require something from parent classAbsence
, you could pass them its reference. Though I would try to avoid that.If you manipulated
Absence
object via base class interface, nothing changes, you can keep your old code. Now, if you manipulated specific derivatives, then you will have to grabAbsenceType
object from specificAbsence
and do all the same things on them - still not much to change. If you hadholiday.DoSomething()
, now you haveholiday.Type.DoSomething()
.