When I was starting in OO programming, mostly Symfony and Doctrine ORM, I have been advised at least one time to apply inheritance mapping for a case as follows:
For instance, a HR system which deals with job candidates, employees, former employees, part time employees etc..
There would be an entity class User, and since there are other kinds of users, like above mentioned, there would be a few more child classes e.g. Employee, etc..
The difference between users is that each type has a set of specific properties. And they all share a set of properties.
So, the shared properties (e.g. ID, Name, Surname) would be in User parent class and the rest, specific ones would be in particular child class.
Looking at Doctrine docs, a neat way to map this, I guess, would be the Single Table Inheritance
I am not giving example of any behaviours for the classes, at this point, and that is on purpose. The case perhaps implies inevitable different implementations of a certain behaviour, which would justify the polymorphism.
I'd like to be sure about how to deal with specific properties first.
Would you agree that Inheritance mapping is justified in this case?
Does the presence of shared and specific properties for a group of real world objects inevitably implies polymorphism?
-
2One problem with that line of thinking is that darn near EVERYTHING could be reduced down to some common root called "Entity" or something. And you end up with situations where a single line of inheritance isn't sufficient, like if you have a User class, which is inheritied the Employee class and the Contractor class, but later on you find that some employees return AS contractors and we need to track them differently from plain employees/contractors, etc etc etc. Basically it is a matter of judgement.GHP– GHP2017年05月26日 13:29:38 +00:00Commented May 26, 2017 at 13:29
1 Answer 1
I would recommend avoiding that inheritance here especially when you are talking about letting that inheritance bleed down into your database - unless its an Object-Oriented database.
But a typical RDBMS is not object-oriented, and anyone who has to write queries against a table structure that's designed in favour of supporting inheritance for your ORM is not going to thank you for it :)
I'd recommend Composition over Inheritance in virtually every case like this, and here is why.
The key part here is this statement
And they all share a set of properties.
That tends to imply that these set of properties should be implemented using Inheritance from some kind of common Base Class i.e. an Employee
is-a Person
, a Candidate
is-a Person
and so on.
e.g.
public class Person
{
public string Name {get;set;}
}
public class Employee:Person
{
public string EmployeeId {get;set;
}
public class Candidate:Person
{
public string CandidateId {get;set;
}
But you're better off tackling this from the perspective of the Domain itself.
Model the Domain Objects to be extremely specific
Although at face value it might seem that this is the best way, and that these properties really are common to all kinds of people, are you really sure?
Certainly all Employees have things like "Name" as do Candidates, but who needs to know that information really depends upon who is asking when we're talking about modeling a Domain with Object-Oriented Programming.
For example, the
Payroll
system will need to know my name in order to print it on my Payslip; so it makes sense for theSalariedEmployee
to have a "Name" property. However theClockInOut
module really couldn't care less; its job is just to record the in and out times for anEmployeeId
so to it I am just aTimeClockedEmployee
with a uniqueId
.
If we've inherited from some common Person class, you have that information carried around the place whether you want or need it.
Now Name
is not that big a deal but what about things like Religion, etc. That kind of data should only be accessible to modules that really need it for several reasons
- Ability to keep a sharp and small focus on the Domain being modeled; having common properties - or worse, behaviour - that are never going to be used by a specific Domain model can makes development and maintenance harder.
- Ability for the specific Domain area being modeled to change without having to affect any other model in the system;
- Data Security, by avoiding exposing data to parts of the system that do not need it
Handling future changes - example
New and as yet undiscovered business processes may require more information that isn't a fit for a base class at all yet is required by more than one Employee
-type object. e.g. Let's say new legislation says you need the Social Security number for both Hourly
and Salaried
employees, but you do not need it for ContractEmployees
(who despite what some might say, are still People
:) )
So now you have to break your nice inheritance to put duplicate properties on both
HourlyEmployee
andSalariedEmployee
anyway or else deal with the possibility for some of your code to deal with objects that can have anull
SSN. Now things are getting messy - does anull
SSN represent aContractor
, or a mapping error? Who knows...
Better approach
In the long run, it's more flexible to model this using Composition whereby we say an HourlyEmployee has-a Person
, a Candidate has-a Person
and so on.
So an HourlyEmployee
, SalariedEmployee
,ContractEmployee
, PotentialEmployee
, VisitorNonEmployee
,PizzaDeliveryPerson
etc, can have (or not) a PersonalInformation
structure if needed, that is indeed common to all those that have it, but which is not part of any hierarchy.
In terms of how this maps to the Database via the ORM, the individual backing tables will have columns for the
PersonalInformation
object, and most ORM systems will support Value Object Mapping to allow you to map that subset of properties into thePersonalInformation
object as a Value object on the Entity in question. YMMV depending on the ORM in question.
e.g. This might store personal information about a person.
public class PersonalInformation
{
public string Name {get;set;}
}
If you really need to pass around Polymorphic objects, then Interfaces are just as useful here as base classes; in fact, because you can implement multiple interfaces, I would say they are more useful.
public interface IEmployee
{
string EmployeeId {get;}
}
This might indicate that an entity holds some personal information:
public interface IPersonalInformationHolder
{
PersonalInformation PersonalInformation {get;}
}
as seen in
public class Employee : IEmployee,IPersonalInformationHolder
{
public string EmployeeId {get;}
public PersonalInformation PersonalInformation {get;}
}
Note how the ClockTimedEmployee
doesn't have any personal information; because we'll never ask it for your name. That will be done by some other part of the system e.g. the Reporting Module. And it can still be passed around as an IEmployee
to any part of the system that cares about EmployeeId
public class ClockTimedEmployee: IEmployee
{
public string EmployeeId {get;set;}
}
Another distinct advantage of this approach is that it guarantees that you can never accidentally pass a ClockTimedEmployee
into any method that expects an IPersonalInformationHolder
; the code simply will not compile. This helps to cut out a whole class of runtime bugs that are hard to avoid with inheritance.
-
Would you agree that the Composition over inheritance is exactly what Strategy pattern is all about?Đuro Mandinić– Đuro Mandinić2017年05月29日 14:37:01 +00:00Commented May 29, 2017 at 14:37
-
1@ĐuroMandinić - yes, that is correct. Although I am sure someone could implement with abstract base classes as well but that would be going against the spirit of the thing :)Stephen Byrne– Stephen Byrne2017年05月29日 21:48:51 +00:00Commented May 29, 2017 at 21:48
-
I've seen somewhere an example with abstract base class. The author says that it does not really matter because the abs. class does the role of interface in that case.Đuro Mandinić– Đuro Mandinić2017年05月30日 09:00:59 +00:00Commented May 30, 2017 at 9:00
-
1@ĐuroMandinić - indeed it doesn't; abstract classes can do the job just as well, except that for example in C# we tend to use interfaces as they are more idiomatic as abstractions. But they key point to remember is to prefer "Has-A" over "Is-A" and you can't go wrong...Stephen Byrne– Stephen Byrne2017年05月30日 09:31:01 +00:00Commented May 30, 2017 at 9:31