Techniques to Decide Between Interface and Abstract Class in Object-Oriented Design — and How to Prevent God Classes and Tight Coupling
Designing software is like composing music — harmony matters. In object-oriented design, that harmony often hinges on how you structure contracts between components. Interfaces and abstract classes both define what an object can do, but they diff...

I am Tuanh.net. As of 2024, I have accumulated 8 years of experience in backend programming. I am delighted to connect and share my knowledge with everyone.
1. The Subtle Art of Choosing Between Interface and Abstract Class
1.1 When an Interface Is the Right Instrument
public interface PaymentProcessor {
void processPayment(double amount);
}
public class PaypalProcessor implements PaymentProcessor {
@Override
public void processPayment(double amount) {
System.out.println("Processing PayPal payment of $" + amount);
}
}
public class CreditCardProcessor implements PaymentProcessor {
@Override
public void processPayment(double amount) {
System.out.println("Processing Credit Card payment of $" + amount);
}
}
1.2 When an Abstract Class Strikes the Balance
public abstract class ReportGenerator {
public void generate() {
fetchData();
process();
export();
}
protected abstract void fetchData();
protected abstract void process();
protected void export() {
System.out.println("Exporting report as PDF...");
}
}
public class SalesReport extends ReportGenerator {
protected void fetchData() {
System.out.println("Fetching sales data...");
}
protected void process() {
System.out.println("Processing sales figures...");
}
}
1.3 Why Not Just Use One of Them for Everything?
2. Avoiding God Classes and Tight Coupling
2.1 Recognizing the God Class Monster
- Thousands of lines of code in one file.
- Methods that reach into other classes’ data.
- Changes in one feature breaking three others (because, surprise — they’re secretly connected).
public class PaymentService {
private PaypalProcessor paypal = new PaypalProcessor();
private CreditCardProcessor creditCard = new CreditCardProcessor();
public void process(String type, double amount) {
if (type.equals("paypal")) {
paypal.processPayment(amount);
} else if (type.equals("creditcard")) {
creditCard.processPayment(amount);
}
}
}
2.2 Refactoring Toward Loose Coupling
public class PaymentService {
private final PaymentProcessor processor;
public PaymentService(PaymentProcessor processor) {
this.processor = processor;
}
public void process(double amount) {
processor.processPayment(amount);
}
}
PaymentService doesn’t care. It just relies on the interface.You can inject the dependency via frameworks like Spring, following Dependency Inversion — the “D” in SOLID.
3. Abstracting Without Over-Engineering
3.1 When Abstraction Becomes a Trap
IAbstractBaseManagerServiceFactoryProvider, congratulations — you’re abstracting yourself into confusion.The key rule: abstract when behavior may vary, not when you’re just afraid of future change.
3.2 The Interface Segregation Principle in Practice
public interface Reader {
void read();
}
public interface Writer {
void write();
}
public class FileHandler implements Reader, Writer {
public void read() { System.out.println("Reading file..."); }
public void write() { System.out.println("Writing file..."); }
}
4. Strategies for Long-Term Maintainable Design
4.1 Use Abstract Classes for “Is-a” Relationships
Shape → Circle, Square).
4.2 Use Interfaces for “Can-do” Capabilities
Serializable, Comparable, Runnable).
4.3 Keep Classes Focused and Cohesive
4.4 Use Dependency Injection to Enforce Loose Coupling
5. Final Thoughts — Designing for Change, Not for Now
Read more at : Techniques to Decide Between Interface and Abstract Class in Object-Oriented Design — and How to Prevent God Classes and Tight Coupling





