开发者

Composition over Inheritance - where do extra properties go?

开发者 https://www.devze.com 2023-03-19 21:04 出处:网络
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

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
{
    开发者_如何学JAVApublic 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)?


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.


I would model this by having a type hierarchy for an AbsenceType, or AbsenseReason:

abstract class AbsenseReason {
}

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

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:

  • NHibernate does not support inheritance mappings on components so you will have to provide a custom implementation of IUserType.
  • Consider storing all the data for the different absence reason sub types together with the record for the employee absence entity. Possibly as XML so that you can have collections, etc.


So try to move all type specific functionality to AbsenceType derivatives. If they require something from parent class Absence, 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 grab AbsenceType object from specific Absence and do all the same things on them - still not much to change. If you had holiday.DoSomething(), now you have holiday.Type.DoSomething().

0

精彩评论

暂无评论...
验证码 换一张
取 消