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

  • Mastering Blazor Authentication and Authorization

    At the Visual Studio Live! @ Microsoft HQ developer conference set for August, Rockford Lhotka will explain the ins and outs of authentication across Blazor Server, WebAssembly, and .NET MAUI Hybrid apps, and show how to use identity and claims to customize application behavior through fine-grained authorization.

  • Linear Support Vector Regression from Scratch Using C# with Evolutionary Training

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the linear support vector regression (linear SVR) technique, where the goal is to predict a single numeric value. A linear SVR model uses an unusual error/loss function and cannot be trained using standard simple techniques, and so evolutionary optimization training is used.

  • Low-Code Report Says AI Will Enhance, Not Replace DIY Dev Tools

    Along with replacing software developers and possibly killing humanity, advanced AI is seen by many as a death knell for the do-it-yourself, low-code/no-code tooling industry, but a new report belies that notion.

  • Vibe Coding with Latest Visual Studio Preview

    Microsoft's latest Visual Studio preview facilitates "vibe coding," where developers mainly use GitHub Copilot AI to do all the programming in accordance with spoken or typed instructions.

  • Steve Sanderson Previews AI App Dev: Small Models, Agents and a Blazor Voice Assistant

    Blazor creator Steve Sanderson presented a keynote at the recent NDC London 2025 conference where he previewed the future of .NET application development with smaller AI models and autonomous agents, along with showcasing a new Blazor voice assistant project demonstrating cutting-edge functionality.

Subscribe on YouTube