Practical .NET

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.

Designing Interfaces
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
End Interface

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
  Inherits IAdjustment
  Property ReadOnly Amount As Decimal
End Interface

Public Interface IPercentageAdjustment
  Inherits IAdjustment
  Property ReadOnly Percentage As Decimal
End Interface

Leveraging Interfaces
With my interfaces in place, I can use them when defining new classes. Here's the start of my PayingCashAdjustment struct:

Public Structure PayingCashAdjustment
  Implements IAbsoluteAdjustment

  Private _Name As String
  Private _Amount As Decimal

  Friend Sub New(Name As String, Amount As Decimal)
    _Name = Name
    _Amount = Amount
  End Sub

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
  Get
    Return _Name
  End Get
End Property

Public ReadOnly Property Amount As Decimal Implements IAbsoluteAdjustment.Amount
  Get
    Return _Amount
  End Get
End Property

Public ReadOnly Property FormattedAmount As String Implements IAbsoluteAdjustment.FormattedAmount
  Get
    Return FormatCurrency(_Amount)
  End Get
End Property

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
  End If

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.

About the Author

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/.

comments powered by Disqus

Featured

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube