I recently had a small “argument” about inheritance with a friend. I think people overuse it a lot. Intuition tells me that that the only good reason for class inheritance is polymorphism – when there is a case that you want to treat uniquely object of different but related classes.
Our argument was about having a BaseEntity class for Entity Framework that only has Id property. My friend says it’s good because he can have generic repositories then. W don’t agree with him.
- Usually there’s no such thing as a generic repository, you’ll end up with “Refused bequest” smell.
- What’s the point of treating let’s say a user and an invoice uniquely? These are 2 totally different things.
Unfortunately I couldn’t make any good points in spite of “it makes no sense”. 🙂
After a while I also realized that I drifted away from inheritance itself and now I tend to use interfaces instead. Actually in my recent web project (quite a big one) I can’t recall any inheritance. Now I’m not sure when it is an appropriate technique anymore.
So guys, please help me here.
- What do you think about what I wrote?
- Can you find any arguments I can use against my friend?
- When it really is a good practice to use inheritance? (Please be specific and throw some examples)
Please don’t try to explain to me what the inheritance is all about. I’m very familiar with that and I used it a lot. Like I mentioned above, over time I just drifted away from it, because I couldn’t find the right usage for it. Simply interfaces seem to be a better approach to me in most cases.
3
Like you, I tend to favor composition over inheritance. However, let me give you an example where I find inheritance works really well…
Let’s say you have a screen where you’re editing a list of User
s. So you have an entity called User
which is your persistence object. Then I prefer to present a flattened model of this to my UI for editing, since there’s usually extra logic and checks going on:
class User
{
public string Name { get; set; }
public DateTime DateOfBirth { get; set; }
}
interface IEditUser
{
string Name { get; set; }
DateTime DateOfBirth { get; set; }
int Age { get; }
void CommitChanges();
void Delete();
}
Now when there’s editing going on, we need to load the list of User
s with the initial values, let them be edited, and then you can choose whether to Save or Cancel those changes. Since handling a newly created User
is much different than handling an existing User
(some fields are only edited in one of those situations, the code for persisting them are different, etc.). That’s why I like to do this:
abstract class EditUserBase : IEditUser
{
public string Name { get; set; }
public DateTime DateOfBirth { get; set; }
public int Age { get { return DateTime.Today.Year - this.DateOfBirth.Year; } }
public abstract void CommitChanges();
public abstract void Delete();
}
Then my class for tracking new users added to the list (but not saved to the database):
class EditNewUser : EditUserBase
{
public EditNewUser()
{
this.Name = string.Empty;
this.DateOfBirth = DateTime.Today;
}
public override void CommitChanges()
{
var user = new User()
{
Name = this.Name,
DateOfBirth = this.DateOfBirth,
};
// persist to database
}
public override void Delete() {} // nothing to do
}
…and finally a class for tracking changes to existing entities:
class EditExistingUser : EditUserBase
{
private readonly User existingUser;
public EditExistingUser(User existingUser)
{
this.existingUser = existingUser;
this.Name = existingUser.Name;
this.DateOfBirth = existingUser.DateOfBirth;
}
public override void CommitChanges()
{
this.existingUser.Name = this.Name;
this.existingUser.DateOfBirth = this.DateOfBirth;
// possibly make database call here
}
public override void Delete()
{
// code to delete this.existingUser from database
}
}
I’ve found this works really well. It separates my editing logic from my entities, and separates the logic of creating a new entity from editing an existing one. It also creates a place to put logic that’s common to both new and existing entities (such as the Age
calculated property, and yes I know it’s not implemented right 🙂