Implementation Inheritance, or Interface Inheritance, that is the Question
We recently covered VB.Net OO topics in our class and the question came up regarding when to Inherit from a base object (Implementation Inheritance) versus when to Implement an interface (Interface Inheritance). While the two approaches are not mutually exclusive, and can be very powerful when used together, let me share my thoughts on the matter.
The Inherits keyword brings all the implementation details of the parent class into the derived class; this is an elegant means of code reuse and allows for polymorphism via inheritance. Take the following object model showing Inheritance:
Public Class BaseBizObject
Private _strState as String
Public Property State As String
Get
return _strState
End Get
Set( val as String )
_strState = val
End Set
End Property
Public Sub LogState()
'save state somewhere...
End Sub
End Class
Public Class PersonBizObject
Inherits BaseBizObject
'Specific Implementation of PersonBizObject Methods
End Class
Public Class OrderBizObject
Inherits BaseBizObject
'Specific Implementation of OrderBizObject Methods
End Class
All our specific business objects (PersonBizObject, OrderBizObject, and any others we create in our business object layer) implement the LogState behaviour courtesy of the BaseBizObject object. We only have to write the code to LogState() in our parent object, and the implementation cascades down to all the derived objects. If we ever needed to implement new functionality common to all our business objects, all we have to do is include the code in the BaseBizObject and voila, we’re good to go. This is how Implementation Inheritance works; it’s great when creating a closely coupled pattern of reuse in an object model. I’ve worked on many projects where we create a base object for whatever application tier we’re working on, just to provide an easy way to implement common functionality (such as LogState()).
The Implements keyword binds a class to a specific contract, but has no impact on implementation; this is an elegant means of code reuse but in a completely different way than the Inherits keyword. This is also known as “Interface Inheritance.” Take the following code:
Public Interface ILoggable
Sub LogState()
End Interface
Public Class PersonObject
Implements ILoggable
Public Sub LogState() Implements ILoggable.LogState
'log Person state to the Person table, text file, whatever
End Sub
End Class
Public Class OrderObject
Implements ILoggable
Public Sub LogState() Implements ILoggable.LogState
'log Order state to the Order table, text file, whatever
End Sub
End Class
The ILoggable Interface enforces a LogState() method on every type that implements it. We don’t have LogState behaviour (i.e. code) we want to share between our PersonObject and OrderObject, but we are interested in binding the objects to the ILoggable contract so that we could write the following code:
Sub EventHandler()
Dim objPerson as New PersonObject()
Dim objOrder as New OrderObject()
DoLogging( objPerson )
DoLogging( objOrder )
End Sub
Sub DoLogging( loggable as ILoggable )
loggable.LogState()
End Sub
Our DoLogging method accepts anything implementing the ILoggable interface and invokes the LogState method. The DoLogging method is where Interfaces create the ability to reuse code – that is, our sub procedure can contain specific code for acting on the ILoggable interface and we don’t have to add the code into each class that implements ILoggable. Granted, this is a trivial example, but it’s a very powerful mechanism. Our Person and Order objects adhere to the ILoggable contract (interfaces are really contracts enforced by the CLR) allowing us to program other objects against the contract instead of the specific objects themselves; this, in turn, allows us to separate our object layers from one another instead of closely coupling all our objects together into a monolithic spaghetti of object relationships. I implement interfaces much more than I program inheritance models for many reasons, including the following:
-Interfaces help me identify the intersections of objects in code, which helps me design a more robust solution.
-Interfaces allow for decoupling of objects (so I don’t need a reference to specific object – I can just reference the Interface type instead).
-Inheritance models must be very carefully designed to actually be reused. Anticipating future uses of code is tricky, and writing very generic base objects and chaining them together via inheritance is slow going, complex to maintain, and often unsuccessful anyway. Determining a contract for behaviour (for example: every ReportObject will provide a method returning a SortedList of Key:Value pairs that my ReportProcessor can interact with …) makes for cleaner design, code and ultimately a higher chance of code reuse.
When considering Inherits versus Implements, remember that in the first case you inherit the implementation, and in the second case you agree to the interface contract.
Happy .Netting