Interfaces vs. Abstract Base Classes
One of the questions I often ask in an interview is this:
In C#, what’s the difference between an interface and an abstract base class? When would you choose one over the other?
This also just came up in Discord, so I thought it might be worth turning my answer into a blog post.
Similarities and Differences
Both interfaces and abstract base classes share one key thing in common: they both are powerful tools that provide the object-oriented concept of abstraction. Abstraction allows you to bring the essential stuff forward while hiding the details behind the scenes.
In particular, both interfaces and abstract base classes allow you to use a particularly powerful form of abstraction: the specific class that performs the job is hidden, allowing you to replace it for another class entirely. With virtual method calls in play, that means we can swap out entirely different behavior without affecting the rest of the program!
But interfaces and base classes are also quite different fundamentally.
Interfaces deal with the outside edge of something–the boundary. I love tortured analogies, so the analogy I’ll use is that if a class is like a peanut M&M, an interface is like defining the thin candy shell around the outside. An interface doesn’t dictate anything about the insides of a type that implements it. It only requires that a type can be interacted with in a particular way.
In contrast, inheritance cuts right to the heart. Inheritance is an extremely strong relationship. It indicates that the derived type is, at its core, fundamentally still the same as the base class. Inheritance lets you define subgroups within the broader group–specializations. But the core remains intact. To torture the analogy further, the base class is the peanut at the heart of the peanut M&M. Derived classes add a chocolate layer around it, but the core in the middle remains.
With an interface, implementers can be anything, they just provide access in a familiar or standardized way, as defined by the interface. With inheritance, subclasses must still be, at their core, that same class, even if it adds more stuff on top of it. So there is far less flexibility.
Abstract base classes with abstract members blur the line a tiny bit. When a class has an abstract member, it leaves part of itself undefined. Some of the type is left as a definition only–much like an interface.
Indeed, the lines start to get quite blurry if an abstract class has no concrete members at all–a pure abstract base class. At this point, the type has become little more than a definition of how to interact with the type–the express purpose of an interface. (As a side note, pure abstract base classes make more sense in languages without interfaces. If you find yourself making one of these in C#, you should usually just make an interface instead.)
The bottom line is that sometimes, it is hard to tell if the thing you’ve got is about how to interact with something or defines the essence of something.
Some Examples
Using Inheritance with Chess Pieces
The first example we’ll use is one from the book: chess pieces.
Each piece moves in different ways, and it will make sense to put that logic into different classes–Pawn
, King
, Rook
, Bishop
, etc.
But all of these pieces are, at their core, chess pieces.
Since the concept of being a chess piece is so fundamental to what these different classes are, a base class feels like the better choice to me.
So you’d define a ChessPiece
base class with, perhaps, an abstract EnumerateLegalMoves
method.
Each of the specific chess piece classes derive from this and provide an implementation for it.
(Code used by all of these specialized types can go into the ChessPiece
base class, but reusing code isn’t the primary purpose of inheritance.)
public abstract class ChessPiece
{
// I might have also used IEnumerable<Square> or List<Square> here.
public abstract Square[] EnumerateLegalMoves(Board currentState);
}
public class King : ChessPiece
{
public override Square[] EnumerateLegalMoves(Board currentState) { ... }
}
Inheritance feels better here, because the derived types are specializations of the base class. At their very core, they are chess pieces. They don’t just let you interact with them as chess pieces.
Using Interfaces with Player Types
Suppose our chess game is augmented to handle a wide variety of player types: ones using a keyboard, ones using a gamepad, ones on the other side of the network, and even AI players.
We will need to ask these players–regardless of how they manage it–to pick a move to make. But in this case, these things do not necessarily share a fundamental essence. They just need to have a predictable, known way to interact with them. In this case, it is about the boundary, not the core essence of the types.
Thus, I’d probably choose to make an IPlayer
interface and have each of the different flavors just implement it:
public interface IPlayer
{
Move ChooseNextMove(Board board);
}
public class KeyboardPlayer
{
public Move ChooseNextMove(Board board) { ... }
}
Getting Fuzzy with Plugins
It is easy to say, “use interfaces when you’re defining just a boundary and use inheritance/abstract base classes when you’re talking about the core essence,” but what happens when the boundary and the core essence are essentially the same thing?
For example, suppose you’re making a plugin system where each plugin needs to be able to inject itself into the system.
Certain types will do this injection, but these types will rarely do more than fill this role.
Do we make an IPlugin
interface, knowing that we’re talking about defining a standard way for the rest of the program to hunt down and activate the plugin? Or do we make an abstract Plugin
class and expect all classes that want to participate in this process to literally be a plugin?
You could arguably go either way here. My rule of thumb is that if you don’t have a compelling reason either way, then the tie-breaker should go to an interface instead of an abstract base class. As we’ve seen, implementing an interface is a much weaker association, which gives us more flexibility in the long term. So unless we have a compelling reason to make the base class, we should just use an interface:
public interface IPlugin
{
void Initialize(SystemRoot root);
}
public class ExtraCaveMobsPlugin : IPlugin
{
public void Initialize(SystemRoot root) { ... }
}
In some situations, we have another option: both!
If plugins nearly always do certain things, we might consider making a base class (or even several) instead of just an interface. Suppose, for example, that the main purpose of these plugins was to allow new mob types to be defined. We could make this abstract base class:
public abstract MobPlugin : IPlugin
{
public void Initialize(SystemRoot root)
{
root.RegisterPlugin(this, Name);
root.MobDefinitions.AddRange(EnumerateMobs());
}
public abstract string Name { get; }
public abstract IEnumerable<MobDefinition> EnumerateMobs();
}
Leaving specific plugins to simply enumerate their mobs instead of writing out all the infrastructure code themselves keeps plugin development easy.
public class ExtraCaveMobsPlugin : MobPlugin
{
public override string Name => "Extra Cave Mobs";
public override IEnumerable<MobDefinition> EnumerateMobs() => new List<MobDefinition>
{
new CaveSpiderDefinition(),
new CaveTrollDefinition(),
new ShadowShrubDefinition()
};
}
In this case, we’ve made life easy for things that do nothing but add new mob types–which might be very common–without removing the option for wildly different plugins to just go implement IPlugin
from scratch.
I will say, though, that this particular design feels just a tiny bit “off” to me.
We’ve made a MobPlugin
base class.
But now suppose we make a BiomePlugin
base class that does similar work to register a new biome in the system.
What happens when we have a plugin that wants to create a fire swamp biome with Rodents of Unusual Size mobs?
C# does not allow for multiple inheritance, and so the SwampBiomePlugin
can’t just derive from both MobPlugin
and BiomePlugin
and do both easily.
We’d have to pick one or the other (or neither, and just implement IPlugin
from scratch) and deal with it.
If this is a common thing, then perhaps it is time to consider wildly different designs instead that ensure common things are easy (and uncommon things still aren’t hard).
The Bottom Line
If you’re just defining a boundary or a standard way the rest of the program will interact with a certain category of objects, then use an interface.
If you’re defining the core or essence of something shared by many specialized types, use a base class.
When you don’t have a compelling reason to use inheritance, use interfaces. They attach fewer strings to the classes that implement it.
It’s okay to get it wrong, though. You can change the code later if you realize you made a mistake.
And this is also an art, and it takes time and practice to grow an intuition about which flavor is better, even if you know the “rules” defined in this post. After making a few hundred interfaces and base classes, and thinking critically about how well they’re working in different contexts, you’ll grow a strong intuition about which works better.