.Net provides two discrete mechanisms of inheritance. These are known as Implementation and Interface Inheritance. There are plenty of articles out on the net that describe what these discrete types of inheritance are in terms of their core characteristics, and the distinctions between them, but very little information about why and when to use each type. As a consequence, many beginner developers tend to use Implementation Inheritance heavily, but take very little advantage of Interfaces at first, knowing their characteristics but being unsure of what they can do is actually useful for. This article provides a brief recap on the similarities and differences between these two types of inheritance, but is primarily intended to clarify not just how and what they do, but when and why you’d actually use them in a real-world design.
Possibly the simplest of the two types of Inheritance to grok the purpose of is Implementation Inheritance: you have a class, and, provided the definition of that class allows you to, you may inherit from that class. When you do so, your inherited class will have all of the same characteristics as the original, plus any further characteristics that you may choose to add. When using the above form of inheritance, you end up with a superclass defining a less specific object, and a subclass defining a more specific object. These class definitions are related in what is known as an inheritance hierarchy. A simple example is an object that defines the basic characteristics of a motor vehicle:
public class Vehicle { public int Speed{ get; private set;} public void Accelerate() { Speed++; } public void Decelerate() { Speed--; } }
Keeping things simple, the basic Vehicle class above defines that objects of type Vehicle will have a read-only integer Property called Speed, and two Methods – one for indirectly incrementing the Speed property, and one for decrementing it, named Accelerate and Decelerate respectively. You can probably think of lots of other things that vehicles might also do. Some of the things you can think of will apply to all vehicles, and others will apply only to specific types of vehicle, such as a motorbike, a racing car or a milk float! The above approach gives you the beginnings of a class structure to add any characteristics that you might need to define for each of those sub-types of vehicle as and when they become relevant to your model, in the most appropriate place.
A simple definition of one of the possible sub-types of vehicle, Car, is shown below:
public class Car : Vehicle { public Car(int Doors) { NumDoors = Doors; } public int NumDoors { get; private set; } }
The above definition declares that objects of type Car inherit from the superclass Vehicle, and so Car objects will consequently have all of the characteristics of Vehicle –type objects. The definition of Car augments the definition of Vehicle by declaring an additional integer Property called NumDoors, which can only be set when instantiating a new object of type Car, and thereafter is only accessible as a read-only Property (which makes sense, since cars generally don’t change the number of doors they have during their lifetime!).
So far so good. Most people learning object-oriented design for the first time tend to understand Implementation Inheritance as described above fairly easily and intuitively, probably because it’s an approach that’s most often used where the developer has control over the full design, perhaps acting as part of a small, self-contained team, and consequently they get to affect and influence the definition of pretty much every class in the various hierarchies that make up the whole design they’re involved in. There are a couple of simple, basic rules to remember, such as you can only inherit from one base class, and some types of class (known as sealed classes) can be deliberately designed so as not to be inheritable from, whilst others (known as abstract classes) can be designed so that they must be inherited from. Overall, though, Implementation Inheritance is a fairly easy concept for most developers to get their head around quickly, and produce useful designs from, that minimises code repetition and promotes separation of concerns. Then comes Interface Inheritance!
The first time you see Interface Inheritance, you could be forgiven for wondering exactly what the point of it is? Firstly, unlike Implementation Inheritance, you can inherit as many Interfaces as you like on a given class (somewhat confusingly, this is commonly referred to as ‘implementing an interface’, rather than ‘Interface Inheritance’, but I’ll use the latter less ambiguous phrase in this article to avoid confusion with the separate topic of ‘Implementation Inheritance’ described above). Secondly, interfaces, unlike base classes, can’t contain any actual implementations of Properties, Methods, etc: they can only define that certain characteristics will exist, and it is for the objects that inherit that Interface to define exactly how they work in relation to the inheriting object.
So, what’s the point in defining objects that you don’t actually provide the inner workings of? Going back to our vehicle example, if we instead defined vehicle type objects as an Interface, it might look something like this:
public interface IVehicle { int Speed { get; set; } void Accelerate(); void Decelerate(); }
Notice that you can’t any longer provide a definition of the inner workings of the Accelerate and Decelerate Methods: you can only define that those Methods will exist, and any arguments they will take. Notice also that you can no longer set the scope for the Properties and Methods that the IVehicle interface defines – every Property and Method is inherently public, and must remain so (even adding the scope ‘public’ to the definition of any Property or Method in an Interface will cause a compilation error, and the compiler will tell you that scope can’t be defined for discrete characteristics of an Interface). This gives a clue as to what the purpose of an Interface actually is: unlike Implementation Inheritance, Interface Inheritance is intended to be of benefit to consumers of third-party objects that inherit them, not the inheriting objects themselves. The above example demonstrates exactly what most new developers find confusing about the purpose of Interfaces: there’s nothing about the IVehicle interface defined above that is inherently useful to designers of the classes that will implement it, since it leaves the majority of the work of defining how the Methods, Properties, etc, that each inheriting class must have up to the designer of the inheriting class, making the Interface itself pretty redundant in terms of code reuse.
The real benefit of Interfaces comes when you need to define some discrete characteristics of a third-party object, the detailed design of which you can’t control, but that will nonetheless be required to be consumable by some other object that you do control. Imagine for example that you’re designing a new data grid, that allows information of an as-yet unspecified form to be presented in an Excel-like format:
Column 1 | Column 2 | Column 3 | Column 4 |
some data... | some data... | some data... | some data... |
some data... | some data... | some data... | some data... |
some data... | some data... | some data... | some data... |
... | ... | ... | ... |
As the designer of this new grid object, you decide that you’d like the available data to be presented as text in ‘normal’ viewing mode, and that when a User clicks on an individual cell, the underlying data will become editable in some detailed way that consumers of your grid will define. So, if the data in question happens to be a person’s first name, then the appropriate control to present for editing that data when the user clicks in a cell containing that type of information will possibly be a Textbox; it’s really for the consumer to decide. If the data is a graphic (and the text represents some meaningful title for the image), the consuming developer might choose to launch the User’s default image editor, and allow the underlying image to be edited there. If the data represents co-ordinates on a map, the control that allows editing to take place might be a complex custom widget that integrates with Google Earth, and allows a set of co-ordinates to be selected by clicking on a 3D map, and returns a text representation of the latitude and longitude that the position clicked by the User represents. The point is, as the designer of the flexible data-editing grid, you don’t know (and won’t want to become overly-concerned with) any of that potential complexity, but you do want to be able to provide a generically-useful grid to facilitate editing, and you do want to be able to present the outcome of any edit operation that might happen within your grid, whilst leaving the complexity of how specific edit operations are implemented for discrete types of data up to other developers with more specific design considerations of their own. To achieve these objectives, you employ an object-oriented design principle known as abstraction. That is, you define a minimum subset of Properties, Methods, etc that any controls that will be used to edit data in your grid must implement, without getting into how those Properties and Methods will be implemented in each specific case. It enables you to provide other developers, who may be working entirely separately from you, with a roadmap of which specific qualities they need to build into their objects in order to ensure that those objects can work in harmony with your design. This is exactly what Interfaces allow you to achieve, and precisely what they are useful for.
Although as the designer of the data grid above you don’t know how complex or simple the data your consumers will need to present on a given occasion will be, and consequently how complex any specific editor controls will be, you can define a relatively-simple interface that consumers of your grid must implement in any controls they want to use for editing, if they want to use their control with the grid you have designed. One simple definition of an Interface to be inherited by an editing control might be:
// IGridCellEditControl definition, // utilising a public delegate public delegate void OnEditCompleteHandler(object oldValue, object newValue); public interface IGridCellEditControl { string TextRepresentation { get; set; } event OnEditCompleteHandler OnEditComplete; }
This simple Interface defines that any controls that will be used to edit data in the grid must as a minimum implement a string Property called TextRepresentation, and an Event called OnEditComplete. For simplicity in this example, the Interface doesn’t declare other characteristics that might additionally be required for a full solution.
With the above Interface defined, the developer of the grid can focus on designing the object they have responsibility for and control over, without concerning themselves too much about the inner workings of any third-party editor controls that may reside within and work in conjunction with their grid. The developer of the grid can therefore focus on the problems that are within their control and sphere of influence, safe in the knowledge that any third-party objects will behave in a certain way as a minimum subset of their behaviour. With that knowledge, the definition of a class that represents one cell within the data grid might look like this:
public class GridCell { public IGridCellEditControl EditControl { get { return EditControl; } set { if (EditControl != null) EditControl.OnEditComplete -= new OnEditCompleteHandler(OnEdit); EditControl = value; DisplayValue = EditControl.TextRepresentation; EditControl.OnEditComplete += new OnEditCompleteHandler(OnEdit); } } public string DisplayValue { get; private set; } private void OnEdit(string oldValue, string newValue) { DisplayValue = newValue; } }
This class defines a GridCell object as having two Properties (a string Property called DisplayValue, and a Property of type IGridCellEditControl called EditControl). It also defines a private Event Handler called OnEdit that is called and updates the displayed value whenever the OnEditComplete Event of the specific object that the EditControl Property refers to fires. This object could be expanded to include functionality like showing/hiding the discrete object EditControl refers to at appropriate times (show when the user clicks the cell, hide when OnEdit updates DisplayValue), determining whether the grid cell is presently editable (if the EditControl Property hasn’t yet been set, editing could be disabled), etc. The main point is, GridCell is able to do all this, without ever needing to know how the object that EditControl refers to actually works. This is where Interfaces add value to an object-oriented design.
To summarise, Implementation Inheritance tends to be most useful in situations where you have total control over all of the objects that will be used in conjunction with your design, and you are able to identify a subset of characteristics that are shared by several discrete objects you wish to model. Faced with this type of design problem, placing any less-specific shared characteristics in a base class (or longer inheritance chain), and inheriting accordingly allows you to reuse code, and separate concerns. Interface Inheritance, on the other hand, is useful in circumstances where you have a small number of discrete Properties, Methods and Events that you require to be implemented by a third-party object, the inner workings of which you don’t have influence over. Whenever a third party component developer requires that you implement an Interface within your object in order to use your class within their design, they’ll be setting that requirement for exactly the same reason in reverse: they’re saying that you’ll be able to take advantage of the benefits that their design facilitates, provided you implement a minimum subset of functionality that they’ve pre-defined in order to act as an interface between your (to them unknown) object and their design.