Abstract Class Or Interface?
Abstract Class Or Interface?
An abstract class may have concrete members. An interface is entirely abstract.
So, which one to use and when?
When designing our systems, we usually should have pluggability foremost in our minds. For optimum modularity, it is preferable not to be hamstrung by concretions. To that end, we will be better served using an interface rather than an abstract class at a boundary.
Why?
Say, we need the ability for customers to pay for their shopping cart via a payment gateway. When thinking about the problem, we know what we need to do; something like
PaymentResult Pay(ShoppingCart cart, CreditCard card);
Other than the models, there are no concrete details. So our first port of call should be an interface, a total abstraction:
public interface PaymentGateway { PaymentResult Pay(ShoppingCart cart, CreditCard card); }
We implement PaymentGateway for PayPal and Stripe – PaypalPaymentGateway and StripePaymentGateway.
We want to implement another PaymentGateway
for Braintree. Since PayPal owns Braintree, say, we find that the PayPal SDK also works for Braintree. Sure, the SDK has some differences between Braintree and PayPay, but much of the code is shared.
As we are starting to write our new BraintreePaymentGateway
, we find that much of our code is the same or similar to what we have in PaypalPaymentGateway
. Should we just cut and paste the code?
That’s not in the spirit of keeping our code DRY (Don’t Repeat Yourself).
How about we put the repeated sections of code into a base class, BasePaypalPaymentGateway
? This new class will exist in addition to the two concrete payment gateway classes: PaypalPaymentGateway
and BraintreePaymentGateway
. It will hold only the code common to both these concrete payment gateway classes. It will also be abstract since we don’t want to be able to instantiate it. And BasePaypalPaymentGateway
implements interface PaymentGateway
.
But why not just derive BraintreePaymentGateway
from PaypalPaymentGatway
and override concrete methods as needed?
When considering the Dependency Inversion Principle (DIP), we discovered that overriding concrete methods is best avoided. When overriding concrete methods, we need to be extremely careful – especially in deep inheritance hierarchies. A seemingly minor change to override, remove overriding or change a concrete method will, in turn, change the behaviour of its children.
For that reason, I prefer to avoid overriding concrete methods but rather introduce an abstract base class and only provide protected
concrete helper methods.
Why BasePaypalPaymentGateway
and not just BasePaymentGateway
? BasePaymentGateway
would incorrectly imply that this is a base class that works for all payment gateways. That is not the case here – BasePaypalPaymentGateway
is only a base class to PayPal-owned payment providers since the PayPal SDK works, say, for both PayPal and Braintree. BTW, I have made this up for illustration purposes only – I would be surprised if PayPal provided an SDK that covers Braintree as well.
Ideally, at boundaries, we keep our design maximally abstract and use interfaces. Additionally, when we find that there is shared code among some or all of the interface implementers, we can extract the common code into an abstract base class.
To answer the question in the title: Interface, definitely, and possibly abstract class at the same time, but only if we have shared code.
Next time, I’ll illustrate more clearly with a series of diagrams, the problems resulting from overriding concrete methods and deriving from volatile base classes.
Leave a Reply
Want to join the discussion?Feel free to contribute!