Search code examples
c#interfacederived-classinterface-segregation-principle

Trying to program to abstractions in C# but neither interfaces nor classes really work


I've been trying to apply SOLID principles more consciously on my current project. Using interfaces to create the abstraction and allowing classes that are handling the dependency injection to provide the concretions has really helped with decoupling some of the code and (hopefully!) making it more maintainable in the long run.

However, here and there I'm hitting a bit of a wall where it seems neither interfaces nor abstract classes work for the reason that there are functions for which I want an implementation defined.

This means:

  • Interfaces will not work since I can't define an implementation and obviously don't want to repeat the code in all implementing classes
  • Abstract classes will not work because I cannot derive from multiple classes

Some super simple code to illustrate the problem:

    public abstract class Vehicle
{
    public void MoveForward()
    {
        // Some code here
        // This implementation is always the same
    }

    public abstract void PerformUniqueAbility(); // This is for the derived class to implement
}

public abstract class RadioSignalBroadcaster
{
    public void StartBroadcast()
    {
        // Some code here
        // This implementation is always the same
    }

    public abstract void PerformUniqueBroadcastingAbility(); // This is for the derived class to implement
}

Now of course what I'd like to do is this:

    public class MyNewClass: Vehicle, RadioSignalBroadcaster
{
    // Class that contains the implementations for both MoveForward() AND StartBroadcast() but also allows me to define
    // bodys for the abstract methods

    public override void PerformUniqueAbility()
    {
        // class specific code here
    }

    public override void PerformUniqueBroadcastingAbility()
    {
        // class specific code here
    }
}

Of course I cannot do this because of the error:

Error CS1721 Class 'MyNewClass' cannot have multiple base classes: 'Vehicle' and 'RadioSignalBroadcaster'

What's the best way to approach these scenarios?


Solution

  • C# classes can only inherit from one base class, but can inherit from any number of interfaces.

    If your goal is to have multiple base classes being inherited to MyNewClass, you could change one of your abstract classes to inherit from the other, for example:

    public abstract class RadioSignalBroadcast : Vehicle
    {
        // Implementation goes here
    }
    
    public class MyNewClass : RadioSignalBroacast
    {
        // Implementation goes here
    }
    

    However, as you can see from this approach, it violates Single Responsibility Principle as now RadioSignalBroadcast (and now MyNewClass) has more than one reason to change (if there's a change to Vehicle or RadioSignalBroadcast logic). Any change that happens to any of the base classes will propagate to all other classes which inherit from those base classes, which may or may not be what you're after.

    What's the best way to approach these scenarios?

    That entirely depends on the design of your application. Questions to ask yourself:

    • Do you require Vehicle and RadioSignalBroadcast to be abstract classes, or can it easily be an interface? By the looks of your implementation, you have a couple of methods which you want to share to your derived classes so I understand you wanting to keep them as base classes, but it's something to keep in mind. Also check out if the implementation of MoveForward and StartBroadcast can have a default interface implementation.
    • Does MyNewClass need to implement both base classes/interfaces? Couldn't two separate classes work out better? Separating out classes like this helps to focus each of the classes to have one single responsibility.
    • If MyNewClass is not truly a Vehicle or a RadioSignalBroadcast (as per the previous point), can this object be composed by a combination of either of the two, for example:
    public class MyNewClass : Vehicle
    {
        private readonly RadioSignalBroadcast radio;
    
        public MyNewClass(RadioSignalBroadcast radio)
        {
            this.radio = radio;
        }
    
        public void DoStuff()
        {
            // Do Stuff
            this.radio.PerformUniqueBroadcastingAbility();
        }
    
        // Implementation goes here
    }
    

    Let me know if you want example or more points to point out.