Coding yourself into a corner
Let’s look at an example. Building an e-commerce application is quite commonplace. Whether it is an online website or a physical point-of-sale system, the app will need to process credit cards. Now, credit card processing is a fairly complex thing, but it is something that lends itself to abstractions.
Let’s say your system will be using the PayStuff payment processor. If you are a strict adherent to the YAGNI principle (which I wouldn’t recommend) then you’ll just go ahead and hard-code the implementation of PayStuff like so:
class PayStuffPaymentProcessor {
processPayment(amount: number) {
console.log(`Processing $${amount} payment via PayStuff...`);
}
}
class Checkout {
private paymentProcessor: PayStuffPaymentProcessor;
constructor() {
this.paymentProcessor = new PayStuffPaymentProcessor();
}
processOrder(amount: number) {
this.paymentProcessor.processPayment(amount);
console.log("Order processed successfully!");
}
}
// Usage
const checkout = new Checkout();
checkout.processOrder(100);
checkout.processOrder(50);
This works fine, I guess. You can process and collect money for orders and all is well. It’s not changeable. And never mind that you can’t unit test it at all. But hey, you aren’t going to need anything more, right? YAGNI for the win!
But oops! PayStuff goes out of business! You need to start using the ConnectBucks processor instead of PayStuff. And the very same day you realize that, your product manager asks you to add support for paying with PayPal and Google Pay. Suddenly, your system is not only hard to test, but it doesn’t even work anymore, and supplying all this new functionality will require some pretty major surgery to your system, right?
Abstraction saves the day
What you should have done instead is realize that you are going to need an abstraction. Thus, you create an interface and write all your code against it and not a specific implementation. Then, instead of creating the implementation on the spot, you defer the implementation decision and “inject” into the constructor the implementation of the abstraction that you want to use.