OOP · Design Principles
Program to an Interface
Reduces the number of subclasses.
- Reduces the number of subclasses.
- Encourages code reuse.
- Makes applications easier to extend.
- Improves maintainability.
- Supports loose coupling.
- Simplifies testing.
Best Practices
- Prefer composition when adding optional features.
- Use inheritance only for a true is-a relationship.
- Keep components independent and reusable.
- Combine behaviors instead of extending classes.
- Design systems with flexibility in mind.
Common Mistakes
- Using inheritance for code reuse.
- Creating deep inheritance hierarchies.
- Building subclasses for every feature combination.
- Ignoring reusable components.
- Mixing unrelated responsibilities in a parent class.
Real-World Problem
Imagine you're developing an Online Payment System. Initially, the application supports only Credit Card Payments, so the PaymentService class directly creates and uses a CreditCardPayment object.
As the business grows, customers request additional payment options such as UPI, Debit Card, Net Banking, and Digital Wallets. Every time a new payment method is introduced, developers modify the PaymentService class to use a different implementation.
Although the application continues to work, the payment module becomes tightly coupled to specific payment methods. Each new payment option requires modifying existing code, increasing maintenance effort and reducing flexibility.
Problem The PaymentService class depends directly on a concrete implementation such as CreditCardPayment. Whenever a new payment method is introduced, the class must be modified. This creates tight coupling between business logic and implementation details, making the application difficult to extend, test, and maintain.
Pain Points The application depends on a specific payment implementation. Supporting new payment methods requires modifying existing classes, increasing the risk of bugs. Testing also becomes more difficult because the implementation cannot be easily replaced.
Solution
The Program to an Interface principle states that software should depend on abstractions (interfaces or abstract classes) rather than concrete implementations. Instead of directly using CreditCardPayment, the PaymentService should depend on a Payment interface. Different payment methods can implement this
interface, allowing new functionality to be added without changing existing business logic.
Java Program 1 – Tight Coupling (Bad Design)

Java Program 2 – Program to an Interface (Good Design)


Benefits
- Reduces tight coupling.
- Makes applications easier to extend.
- Supports polymorphism.
- Improves testability.
- Encourages reusable components.
- Simplifies maintenance.
- Works well with SOLID principles, especially OCP and DIP.
Best Practices
- Program against interfaces instead of concrete classes.
- Use dependency injection where appropriate.
- Keep interfaces focused and meaningful.
- Replace implementations without modifying business logic.
- Design for flexibility from the beginning.
Common Mistakes
- Depending directly on concrete classes.
- Creating objects using new inside business logic.
- Writing interfaces with unrelated methods.
- Ignoring abstraction layers.
- Confusing interfaces with implementations.
Code against abstractions so implementations can be swapped freely.
import java.util.*;
public class Main {
public static void main(String[] args) {
// Declare the interface type, not the concrete class
List<String> names = new ArrayList<>();
names.add("Ada");
// Switching to LinkedList changes ONE line; the rest still compiles
// List<String> names = new LinkedList<>();
process(names);
}
// Accepts ANY List implementation
static void process(List<String> items) {
System.out.println(items.size());
}
}