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

  • Hands On: New VS Code Insiders Build Creates Web Page from Image in Seconds

    New Vision support with GitHub Copilot in the latest Visual Studio Code Insiders build takes a user-supplied mockup image and creates a web page from it in seconds, handling all the HTML and CSS.

  • Naive Bayes Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the naive Bayes regression technique, where the goal is to predict a single numeric value. Compared to other machine learning regression techniques, naive Bayes regression is usually less accurate, but is simple, easy to implement and customize, works on both large and small datasets, is highly interpretable, and doesn't require tuning any hyperparameters.

  • VS Code Copilot Previews New GPT-4o AI Code Completion Model

    The 4o upgrade includes additional training on more than 275,000 high-quality public repositories in over 30 popular programming languages, said Microsoft-owned GitHub, which created the original "AI pair programmer" years ago.

  • Microsoft's Rust Embrace Continues with Azure SDK Beta

    "Rust's strong type system and ownership model help prevent common programming errors such as null pointer dereferencing and buffer overflows, leading to more secure and stable code."

  • Xcode IDE from Microsoft Archrival Apple Gets Copilot AI

    Just after expanding the reach of its Copilot AI coding assistant to the open-source Eclipse IDE, Microsoft showcased how it's going even further, providing details about a preview version for the Xcode IDE from archrival Apple.

Subscribe on YouTube

Upcoming Training Events