7

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)?

rObiwahn
  • 238
  • 1
  • 5
Paul T Davies
  • 2,527
  • 2
  • 22
  • 39

5 Answers5

4

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.

djna
  • 54,992
  • 14
  • 74
  • 117
  • Three answers all saying the same thing, very hard to choose between them, but this one just had the edge because it considered audit trails etc. Mike Mozhaev and Ray Toal get upvotes but this get the tick! – Paul T Davies Jul 15 '11 at 07:49
2

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.

Mike Mozhaev
  • 2,367
  • 14
  • 13
1

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.

Ray Toal
  • 86,166
  • 18
  • 182
  • 232
1

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.
eulerfx
  • 36,769
  • 7
  • 61
  • 83
  • I did conceive that there would be SicknessAbsenceType, HolidayAbsenceType inheriting from AbsenceType. I should probably have explained that better in my post. However, I had conceived it as an entity, not a value. I'm not sure how you are suggesting I would store the information specific to an absence in the actual absence type? In my concept it would only contain information about holidays /sickness in general, not about one specific instance. I would be interested to know more about your theory? – Paul T Davies Jul 15 '11 at 07:43
0

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().

driushkin
  • 3,531
  • 1
  • 24
  • 25
  • I'm not sure where you're suggesting I store holiday/sickness specific properties? – Paul T Davies Jul 15 '11 at 07:45
  • so yeah, `AbsenceType` would be kind of Value Type, with no identity, would belong solely to single `Absence` object, and all type specific behavior should be moved to its derivatives: `SicknessAbsenceType`, `HolidayAbsenceType`, ... – driushkin Jul 15 '11 at 08:09
  • This is a simmilar answer to eulerfx. It seems to be a pattern that people are using that I am not totally fammiliar with. I still don't quite grasp it. Could you point me to a code example please? – Paul T Davies Jul 15 '11 at 10:26