Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Modeling (and mapping) a class-hierarchy with two degrees of polymorphism?

I'm running into a situation where I have two levels of polymorphism, one within the other, in a parent/child hierarchy.

I think this is best explained by a simple example:

    class Group
    {
        public IList<Person> People { get; set; }
    }

    class SpecialGroup : Group
    {
        public IList<SpecialPerson> People { get; set; }
    }

    class Person {}

    class SpecialPerson : Person {}

So a Group has a list of Person objects, while a specialized Group (SpecialGroup) has a specialized list of Person (SpecialPerson) objects.

This compiles, but I get a warning saying I should use the "new" keyword on SpecialGroup.People, because it hides the Group.People property.

I understand what that means, but perhaps I don't fully comprehend how you would model something like this correctly in C# to begin with. Any thoughts on how to model this in a better way?

Also, any idea how this would play with NHibernate? I'm not sure using the "new" keyword will cut it, since the Group.People property would already be mapped with a different type. Is there a better way to model this, in a way that is compatible with NH?


Okay, I thought of a way to model this using generics:

    abstract class AbstractGroup<PersonType>
        where PersonType : Person
    {
        public IList<PersonType> People { get; set; }
    }

    class Group : AbstractGroup<Person>
    {}

    class SpecialGroup : AbstractGroup<SpecialPerson>
    {}

    class Person {}

    class SpecialPerson : Person {}

I think this does what I want? Now Person can share traits with SpecialPerson, but only a SpecialPerson can be added to a SpecialGroup. The AbstractGroup base-type can model the traits common to any Group.

Now the question is, will NH blow up if I try to map this?


For the record, it appears some people have successfully used query-over with generic classes - for HQL queries (my use-case) this is unsupported.

Another approach that occurred to me, is to simply hide the parent's implementation of a property, e.g. using the "new" keyword for an overriding property in a subclass - the following thread discusses why that won't work either:

NHibernate: Table Per Subclass Mapping and the New Keyword

Conclusion thus far: run-time checking and exceptions, as proposed by @empi below.

like image 292
mindplay.dk Avatar asked Jan 30 '26 14:01

mindplay.dk


2 Answers

That case is also called "parallel" or "dual" inheritance.

Usually there is a specific class in one hierarchy, that relates to the other.

Altought, not required, its better to have a "generic" or "abstract" class of each one, that must be overriden, but, already has the concept of dependency.

This case its very used in Graphic Interface Controls, but, may apply to other scenarios.

In this particular case, Its usually better to only display the first level:

............................................................
....+----------------+................+----------------+....
....|  <<Abstract>>  |..<<contains>>..|  <<Abstract>>  |....
....| AbstractGroup  +<>--------------+ AbstractPerson |....
....|                |................|                |....
....+----------------+................+----------------+....
............................................................

Altought, you can model several levels, this is not recomended:

............................................................
....+----------------+................+----------------+....
....|  <<Abstract>>  |..<<contains>>..|  <<Abstract>>  |....
....| AbstractGroup  +<>--------------+ AbstractPerson |....
....|                |................|                |....
....+-------+--------+................+--------+-------+....
............|..................................|............
............|..................................|............
............^..................................^............
............|..................................|............
............|..................................|............
....+-------+--------+................+--------+-------+....
....|  <<Concrete>>  |..<<contains>>..|  <<Concrete>>  |....
....|      Group     +<>--------------+     Person     |....
....|                |................|                |....
....+----------------+................+----------------+....
............|..................................|............
............|..................................|............
............^..................................^............
............|..................................|............
............|..................................|............
....+-------+--------+................+--------+-------+....
....|  <<Concrete>>  |..<<contains>>..|  <<Concrete>>  |....
....|   SchoolGroup  +<>--------------+     Student    |....
....|                |................|                |....
....+----------------+................+----------------+....
............................................................

You mention NHibernate, does your classes represent data to be stored ?

Some similar questions:

Avoiding parallel inheritance hierarchies

Parallel Inheritance Hierarchy Refactoring

How to avoid parallel inheritance hierarchies among GUI controls and domain objects

Parallel Inheritance between Interface Classes and Implementation Classes in C++

Cheers.

like image 92
umlcat Avatar answered Feb 01 '26 08:02

umlcat


If you want to enforce the rule that SpecialGroup contains only SpecialPerson then you may do it programatically without shadowing People property. I guess that you have some method AddPerson. You may override it in SpecialGroup and throw exception if someone tries to add entry that's not SpecialPerson. If you want get the list of SpecialPerson then you may add method IEnumerable<SpecialPerson> GetSpecialPeople() and cast Person to SpecialPerson (it will always be a valid cast since you're checking type in AddPerson method). By doing it you don't have to shadow People property and NHibernate will work just fine.

like image 29
empi Avatar answered Feb 01 '26 08:02

empi



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!