What are the SOLID principles, and how do you apply them to a small design?
SOLID is five principles that all push in the same direction: small, focused units with narrow contracts between them. Single responsibility says a class should have one reason to change. Open for extension and closed for modification says you should be able to add behaviour by adding code rather than by editing code that already works. Liskov substitution says a subclass must be usable wherever its parent is. Interface segregation says clients should not be forced to depend on methods they do not use. Dependency inversion says high level policy should not depend on low level detail, and both should depend on abstractions.
public class Main {
public static void main(String[] args) {
Invoice invoice = new Invoice(new PercentDiscount(10));
invoice.add(new Item("Book", 20.0));
invoice.add(new Item("Pen", 5.0));
System.out.println(invoice.total());
}
}
interface Discount {
double apply(double amount);
}
class PercentDiscount implements Discount {
private final double percent;
PercentDiscount(double percent) { this.percent = percent; }
public double apply(double amount) { return amount * (1 - percent / 100); }
}
class Item {
final String name;
final double price;
Item(String name, double price) { this.name = name; this.price = price; }
}
class Invoice {
private final java.util.List<Item> items = new java.util.ArrayList<>();
private final Discount discount;
Invoice(Discount discount) { this.discount = discount; }
void add(Item item) { items.add(item); }
double total() {
double sum = 0;
for (Item i : items) { sum += i.price; }
return discount.apply(sum);
}
}The invoice example touches several of them. The invoice class has a single reason to change, which is the way totals are calculated. The discount is a strategy behind an interface, so adding a fixed amount discount or a loyalty discount means writing a new class and nothing else. The invoice does not know what kind of discount it is using, which is the dependency inversion principle at work: the high level policy depends on the abstraction rather than on a concrete calculator.
The important qualification is that SOLID is not a checklist to apply mechanically. Each principle costs something. Narrow contracts mean more interfaces, and each one is a place where a change can ripple. The practical goal is not maximal decomposition but classes that change for one reason each, and the ability to add a variation without editing working code.