Generics Constraints: Use Interfaces over Base Class

Prologue:


I’m a big fan of Interface with a Base Class which implement that interface with base behavior. Providing this two together allows the programmer to decide if he wants to inherit from the Base Class and override only the things he really wants or to implement the Interface from scratch.


Here is an example from our code:


1). The Interface:


public interface IPersistentEntity
{
   string ToXml();
   string GetKey();
   // … snippet …
}


2). The Base Class that implements the interface:


/// <summary>
/// Base class for a Business Entity.
/// </summary>
public abstract class EntityBase : IPersistentEntity
{
   public string GetKey()
   {
      return GetKeyCore();   
   }

   protected abstract string GetKeyCore();

   public virtual string ToXml()
   {
      // Build the xml via Reflection over the current type.
   }

   // … snippet …
}



I constraint my EntityCollection object to EntityBase:


/// <summary>
/// Represents a strongly typed list of Business Entities.
/// </summary>
/// <typeparam name=”ENT”>Entity type which inherits from EntityBase class.</typeparam>
[Serializable]
public class EntityCollection<ENT> : List<ENT>, ISerializable
   where ENT : EntityBase
{
   // … snippet …
}


As you can tell, I’m constraining the ENT generic type to be any object that inherit from EntityBase.


The Story:


My teammate, Moran (Yes, him again, goshh… he really challenges me to think deeply about our infrastructure), wanted to create a new EntityCollection of ISpecificEntity (i.e: EntityCollection<ISpecificEntity>). That means that he wanted to extend the IPersistantEntity I showed above.

The problem was that my EntityCollection made a constraint on the generic type to inherit from EntityBase.

That means that the interface should inherit from EntityBase class which is obviously not possible. so his interface should have been some sort of another SpecificEntity class that inherit from EntityBase. Well, that’s no good as some of our existing entities already inherit from the EntityBase and Moran wanted to use them in the collection. That means that Moran should have change the signature of this class (among others):


public class Zone : SpecificEntity //EntityBase
{
   // … snippet …
}


The problem was that Zone was inherited also by another class and we couldn’t change that. To make a long story short, our  headaches were a result of one simple fact:
One of the biggest drawback of inheriting from a Base Class is [your answer here].
You right, the biggest drawback is that you can do it only *once*.


Using abstract classes with interfaces behind is a very good practice, in my book anyway, but using it(abstract class) as a constraint in a Generic Type can cause you some problems later on. The only thing we did was to change the signature of EntityCollection to:


public class EntityCollection<ENT> : List<ENT>, ISerializable
   where ENT : IPersistentEntity
{
   // …
}


That made our life easier as we can always implement additional interface(s) in our class.


Epilogue:


The drawback of single inheritance from class is a dangerous pitfall you should avoid while working with Generics constraints, so my tip for you is:

Constraint your generic type(s) by Interface and not by Base Class.

 

Oren Ellenbogen

 

3 thoughts on “Generics Constraints: Use Interfaces over Base Class

  1. Dear Oren.
    When I went to you blog’s address, the last thing I’ve expected to find is the word "Prolog" written in large letters.
    Anyway, the word you’ve wanted is "Prologue".
    Usually I do not get emotional over misspelling, but after dealing with some Prolog predicates over tree searches algorithms during the last few hours …, well, you can’t blame me.

    And to the actual comment:
    I say, always design against an Interface.
    The notion of "I prefer using Base Classes in case I’d want to change something and then my code would be broken, oh god, what would I do then" is only acceptable if you’re lazy, or designing huge frameworks.
    You should design your model well enough so that the interfaces would do not only to current situation, but to future developments. If something came up that require the change – then it’s one of the three: 1. You haven’t thought the initial design through, or 2. You do not really need the change now, and can manage with the current Interfaces, or 3. you should change the Interfaces, since the design needs changed that bad.
    If you got to no. 3 and want an easy shortcut by using Base Classes the first place, then that means that you have a major mismatch between current design and the implementation.
    I find languages like ADA (and Pascal, for the matter) very smart in the requirement of interfacing each and every class. It’s really making you think hard before you start your implementations.
    I think that designing against Interfaces is the first and most important thing that you can do, in order to actually create well formed, fully scalable systems, (and achieve SOA/WebService/DesignByContract/ChooseYourFavorateBuzzword).
    But I’m tired and grumpy, so you might be right after all.

  2. I completely agree with Ken and I can only add another rule of thumb I’ve come to follow over the years:

    If all communication between classes should be done through interfaces then every class should also expose an interface of its public members. But, hey, this leads to duplication and dual maintainence – and that is evil!

    If you find yourself in this situation what you should do is take another look at your interface. A good interface is one that is implemnted by at least two completely unrelated classes in two completely different ways. If you achieve this you will have achieved encapsulation because now you are sure that whatever is the same in both situations is in one place and whatever is different is in another.

    Using an interface which is implemented only once is very similar (though not quite the same) to referencing a class directly. If you find yourself using such interface you should consider merging the calling class with the called class in the name of cohesion because these classes are tightly coupled.

Comments are closed.