Leveraging Interfaces Instead of Inheritance
If you need to have objects look alike but don't have any code to share, you don't need inheritance -- you need an interface. Here's an example of how interfaces provide a more flexible way to deal with similar-but-different classes.
You have a set of classes that you want to use interchangeably but that hold different code. Most developers reach for the obvious tool: inheritance. But what if you don't have any common code to put in your base class? In that scenario, inheritance is overkill. Alternatively, you might have a diverse set of classes that you want to work with -- creating the right inheritance tree for all of those classes could be difficult.
Interfaces are the better answer. I've been working with a case study where I've been developing value types using structs, rather than classes. Structs don't even support inheritance so I have no choice but to use interfaces if I want to do anything interesting. However, even if I was working with classes, interfaces would be my best choice.
In my case study, I have a set of Adjustment objects that reflect discounts that can be applied to a sales order. Some adjustments are absolute ("$5.00 off for paying in cash!") and some are percentage based ("5% off for buying two or more!").
Obviously, I need two different kinds of Adjustments: an AbsoluteAdjustment and a PercentageAdjustment so that I can keep the two types of changes separate. For example, I shouldn't mix AbsoluteAdjustments and PercentageAdjustments when sorting adjustments (is a 5 percent PercentageAdjustment greater or less than a $5.00 AbsoluteAdjustment -- I can't tell without applying it to a sales order). Equally obvious, there are going to be times when I want to be able to process all of the Adjustments for a SalesOrder, regardless of what kind of Adjustment it is.
To support the "process all adjustments" scenario, I define an interface that all Adjustments will share. All Adjustments have a Name property ("PayingCash," "MultiplePurchases") and a FormattedAmount that returns an appropriate string for displaying the Adjustment. Here's what that interface looks like:
Public Interface IAdjustment
Property ReadOnly Name As String
Property ReadOnly FormattedAmount As String
Now I design the interfaces for when I need to keep AbsoluteAdjustments (like PayingCash) and PercentageAdjustments (like MultiplePurchases) separate. Both have Decimal properties, but the names of the properties are different. While I can't use inheritance when defining structs, I can use inheritance to extend my IAdjustment interface into two new interfaces, IAbsoluteAdjustment and IPercentageAdjustment, like so:
Public Interface IAbsoluteAdjustment
Property ReadOnly Amount As Decimal
Public Interface IPercentageAdjustment
Property ReadOnly Percentage As Decimal
With my interfaces in place, I can use them when defining new classes. Here's the start of my PayingCashAdjustment struct:
Public Structure PayingCashAdjustment
Private _Name As String
Private _Amount As Decimal
Friend Sub New(Name As String, Amount As Decimal)
_Name = Name
_Amount = Amount
Listing 1 shows the implementation of the three properties required by the IAbsoluteAdjustment interface and its base.
Listing 1: Implementing an Interface
Public ReadOnly Property Name As String Implements IAbsoluteAdjustment.Name
Public ReadOnly Property Amount As Decimal Implements IAbsoluteAdjustment.Amount
Public ReadOnly Property FormattedAmount As String Implements IAbsoluteAdjustment.FormattedAmount
A MultiplePurchases Adjustment would look very similar, but would use the IPercentageAdjustment interface. The IPercentageAdjustment struct would have very different code in its FormattedAmount method to display the percentage used with a percentage adjustment.
Using the Interfaces
I can now use the IAdjustment interface to declare a variable to loop through all of the Adjustments for a SalesOrder:
Dim adjusts As List(of IAdjustment)
adjusts = AdjustmentFactory.GetAdjustmentsForASalesOrder("B456")
ForEach adj as IAdjustment in adjusts
When I want to use one of the two more specialized interfaces (IAbsoluteAdjustment and IPercentageAdjustment), I can check to see which interface an Adjustment is implementing:
ForEach adj as IAdjustment in adjusts
If adj Is IAbsoluteAdjustment Then
'...code for working with absolute adjustment
Here's another benefit of using interfaces: Let's say you have some other class that you want to use as an Adjustment. To do that, you just have your class implement one of the two interfaces (IAbsoluteAdjustment or IPercentageAdjustment) and put some code in the interface's methods. Typically, the code you put in the interface's methods will call other code that already exists in the class. The beauty of this process is that this new Adjustment class doesn't have to change its inheritance structure in order to become an "Adjustment-compatible" class.
I have nothing against inheritance … but it's not the only tool in your toolbox. If you don't have code to share and you need some additional flexibility, you should consider using interfaces first.
Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.