Inheritance vs enum properties in the domain model
Asked Answered
J

6

47

I had a discussion at work regarding "Inheritance in domain model is complicating developers life". I'm an OO programmer so I started to look for arguments that having inheritance in domain model will ease the developer life actually instead of having switches all over the place.

What I would like to see is this :

class Animal {

}

class Cat : Animal {

}

class Dog : Animal {

}

What the other colleague is saying is :

public enum AnimalType {
    Unknown,
    Cat,
    Dog
}

public class Animal {

    public AnimalType Type { get; set; }

}

How do I convince him (links are WELCOME ) that a class hierarchy would be better than having a enum property for this kind of situations?

Thanks!

Johppa answered 23/11, 2010 at 8:48 Comment(4)
Is this for an in-memory object model or an ORM? I don't find the Animals example very useful since it doesn't exercise many of the common issues that you face when doing real OO design.Annoying
@Marcelo: agreed. Specifically, what's the behaviour angle?Nils
Dogs bark, cats meow, using your colleague's implementation, these behaviors will be hard to implement in C# (for instance). It's still useful in functional languages like F# where discriminated unions are a very powerful means to provide polymorphism.Monocyclic
@Marcelo and @sfinnie: it's an anemic domain model for ORM and business layer. From my point of view a domain model class can have properties that have no meaning to other classes.Johppa
G
29

Here is how I reason about it:

Only use inheritance if the role/type will never change. e.g.

using inheritance for things like:

Fireman <- Employee <- Person is wrong.

as soon as Freddy the fireman changes job or becomes unemployed, you have to kill him and recreate a new object of the new type with all of the old relations attached to it.

So the naive solution to the above problem would be to give a JobTitle enum property to the person class. This can be enough in some scenarios, e.g. if you don't need very complex behaviors associated with the role/type.

The more correct way would be to give the person class a list of roles. Each role represents e.g an employment with a time span.

e.g.

freddy.Roles.Add(new Employement( employmentDate, jobTitle ));

or if that is overkill:

freddy.CurrentEmployment = new Employement( employmentDate, jobTitle );

This way , Freddy can become a developer w/o we having to kill him first.

However, all my ramblings still haven't answered if you should use an enum or type hierarchy for the jobtitle.

In pure in mem OO I'd say that it's more correct to use inheritance for the jobtitles here.

But if you are doing O/R mapping you might end up with a bit overcomplex data model behind the scenes if the mapper tries to map each sub type to a new table. So in such cases, I often go for the enum approach if there is no real/complex behavior associated with the types. I can live with a "if type == JobTitles.Fireman ..." if the usage is limited and it makes things easer or less complex.

e.g. the Entity Framework 4 designer for .NET can only map each sub type to a new table. and you might get an ugly model or alot of joins when you query your database w/o any real benefit.

However I do use inheritance if the type/role is static. e.g. for Products.

you might have CD <- Product and Book <- Product. Inheritance wins here because in this case you most likely have different state associated with the types. CD might have a number of tracks property while a book might have number of pages property.

So in short, it depends ;-)

Also, at the end of the day you will most likely end up with a lot of switch statements either way. Let's say you want to edit a "Product" , even if you use inheritance, you will probably have code like this:

if (product is Book) Response.Redicted("~/EditBook.aspx?id" + product.id);

Because encoding the edit book url in the entity class would be plain ugly since it would force your business entites to know about your site structure etc.

Gnash answered 23/11, 2010 at 9:57 Comment(4)
We managed to have "Table-per-Hierarchy Inheritance" (msdn.microsoft.com/en-us/library/bb738443.aspx) in EF 4 so the queries are not so ugly.Johppa
Yes table per hierarchy makes the database a bit more slick, however, how much xml did you have to write in order to make it work? it might be more than the switchstatement, just saying.Gnash
We avoided writing xml with database first and than creating the rest of the mapped entities by hand and mapping them to the wright table.Johppa
I would fight to the end to use whatever "Replace Conditional With Polymorphism"-type refactorings I could, to avoid switching on object types OR enum values altogether.Scald
J
13

Having an enum is like throwing a party for all those Open/Closed Principle is for suckers people.

It invites you to check if an animal is of a certain type and then apply custom logic for each type. And that can render horrible code, which makes it hard to continue building on your system.

Why?

Doing "if this type, do this, else do that" prevents good code.

Any time you introduce a new type, all those ifs get invalid if the new type is not handled. In larger systems, it's hard to find all those ifs, which will lead to bugs eventually.

A much better approach is to use small, well-defined feature interfaces (Interface segregation principle).

Then you will only have an if but no 'else' since all concretes can implement a specific feature.

Compare

if (animal is ICanFly flyer)
  flyer.Sail();

to

// A bird and a fly are fundamentally different implementations
// but both can fly.
if (animal is Bird b)
   b.Sail();
else if (animal is Fly f)
   b.Sail();

See? the former one needs to be checked once while the latter has to be checked for every animal that can fly.

Julian answered 9/5, 2011 at 7:59 Comment(3)
if (animal.Type == AType.Dog) {apply logic} vs if(animal is Dog) {apply logic} How is the second example better than the first when it comes to applying concrete type logic?Ephialtes
@mxmissile: Moved my replace to the answer.Julian
should say "reply" and not "replace"Julian
C
11

Enums are good when:

  1. The set of values is fixed and never or very rarely changes.
  2. You want to be able to represent a union of values (i.e. combining flags).
  3. You don't need to attach other state to each value. (Java doesn't have this limitation.)

If you could solve your problem with a number, an enum is likely a good fit and more type safe. If you need any more flexibility than the above, then enums are likely not the right answer. Using polymorphic classes, you can:

  1. Statically ensure that all type-specific behavior is handled. For example, if you need all animals to be able to Bark(), making Animal classes with an abstract Bark() method will let the compiler check for you that each subclass implements it. If you use an enum and a big switch, it won't ensure that you've handled every case.

  2. You can add new cases (types of animals in your example). This can be done across source files, and even across package boundaries. With an enum, once you've declared it, it's frozen. Open-ended extension is one of the primary strengths of OOP.

It's important to note that your colleague's example is not in direct opposition to yours. If he wants an animal's type to be an exposed property (which is useful for some things), you can still do that without using an enum, using the type object pattern:

public abstract class AnimalType {
    public static AnimalType Unknown { get; private set; }
    public static AnimalType Cat { get; private set; }
    public static AnimalType Dog { get; private set; }

    static AnimalType() {
        Unknown = new AnimalType("Unknown");
        Cat = new AnimalType("Cat");
        Dog = new AnimalType("Dog");
    }
}

public class Animal {
    public AnimalType Type { get; set; }
}

This gives you the convenience of an enum: you can do AnimalType.Cat and you can get the type of an animal. But it also gives you the flexibility of classes: you can add fields to AnimalType to store additional data with each type, add virtual methods, etc. More importantly, you can define new animal types by just creating new instances of AnimalType.

Confirmatory answered 23/11, 2010 at 17:17 Comment(0)
Z
8

I'd urge you to reconsider: in an anemic domain model (per the comments above), cats don't behave differently than dogs, so there's no polymorphism. An animal's type really is just an attribute. It's hard to see what inheritance buys you there.

Zeitler answered 23/11, 2010 at 17:38 Comment(1)
Thanks.. "cats don't behave differently than dogs, so there's no polymorphism." seems to be a very good rationale.Dicky
R
1

Most importantly OOPS means modeling reality. Inheritance gives you the opportunity to say Cat is an animal. Animal should not know if its a cat now shout it and then decide that it is suppose to Meow and not Bark, Encapsulation gets defeated there. Less code as now you do not have to do If else as you said.

Redding answered 23/11, 2010 at 9:56 Comment(0)
C
1

Both solutions are right. You should look which techniques applies better to you problem.

If your program uses few different objects, and doesn't add new classes, its better to stay with enumerations.

But if you program uses a lot of different objects (different classes), and may add new classes, in the future, better try the inheritance way.

Cao answered 3/5, 2011 at 16:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.