SECTION 02 / 6

SOLID: five principles for maintainable object-oriented design

3 min read
On this page 6 headings

SOLID is a set of object-oriented principles for software design, conceptualised by Robert Martin (“Uncle Bob”), one of the founders of the Agile Manifesto. They make code easy to understand and modular, and support good cohesion and loose coupling. S = Single Responsibility, O = Open–Closed, L = Liskov Substitution, I = Interface Segregation, D = Dependency Inversion.

Why Design Quality Matters
Why Design Quality Matters
Open image ↗
Why Design Quality Matters
Poor Design vs Good Design
Poor Design vs Good Design
Open image ↗
Poor Design vs Good Design
Refactoring for Single Responsibility
Refactoring for Single Responsibility
Open image ↗
Refactoring for Single Responsibility

2.1 S — Single Responsibility Principle (SRP)

Every class should have a single responsibility/purpose. The original Seller class held seller ID, name, address and product, and had addSeller, deleteSeller, addProduct, and add/delete/view inventory.

Split it into Seller (seller details, addSeller, deleteSeller), Product, and Inventory (takes seller ID; products are added to the seller’s inventory). This lets Product change without major changes to Seller/Inventory, and makes mistakes easier to find because the code is isolated.

Seller Has Too Many Responsibilities
Seller Has Too Many Responsibilities
Open image ↗
Seller Has Too Many Responsibilities
Separate Seller, Product and Inventory
Separate Seller, Product and Inventory
Open image ↗
Separate Seller, Product and Inventory
Single Responsibility Principle
Single Responsibility Principle
Open image ↗
Single Responsibility Principle

2.2 O — Open–Closed Principle (OCP)

Software entities should be open for extension, closed for modification: extend behaviour, e.g. through subclasses, without modifying existing classes. Product has ID, name, type and cost. Adding size/colour and a new addProduct to Product violates OCP.

Instead, create Clothing extending Product with size and colour. A Book subclass with author and publisher is added the same way.

Existing Product Design
Existing Product Design
Open image ↗
Existing Product Design
Open–Closed Principle: Extend without Rebuilding
Open–Closed Principle: Extend without Rebuilding
Open image ↗
Open–Closed Principle: Extend without Rebuilding
Open–Closed Principle: Modify vs Extend
Open–Closed Principle: Modify vs Extend
Open image ↗
Open–Closed Principle: Modify vs Extend
Extend Product with New Classes
Extend Product with New Classes
Open image ↗
Extend Product with New Classes

2.3 L — Liskov Substitution Principle (LSP)

LSP applies to inheritance hierarchies. “Derived classes should be substitutable by their base class”: if D is a subtype of B, we can replace B with D without interrupting the behaviour of the program. The feature is to notify sellers about feedback on their product by email.

Abstract class FeedbackNotification has abstract notify(product, feedback, email); EmailNotification extends it. Adding SMSNotification breaks this: it needs a mobile number, not an email, so the derived method cannot substitute the base one.

Fix: remove email from the abstract notify(product, feedback). EmailNotification takes email in its constructor; SMSNotification takes mobile number and carrier in its constructor. Both implement notify(product, feedback).

Original Email Notification Design
Original Email Notification Design
Open image ↗
Original Email Notification Design
The Substitution Problem: Email vs SMS
The Substitution Problem: Email vs SMS
Open image ↗
The Substitution Problem: Email vs SMS
Shared FeedbackNotification Contract
Shared FeedbackNotification Contract
Open image ↗
Shared FeedbackNotification Contract
Interchangeable Notification Implementations
Interchangeable Notification Implementations
Open image ↗
Interchangeable Notification Implementations
Refactoring for Substitutability
Refactoring for Substitutability
Open image ↗
Refactoring for Substitutability

2.4 I — Interface Segregation Principle (ISP)

An interface is a group of related methods with empty bodies; it specifies what, not how. Other classes implement it by implementing all its methods. Interface Segregation: “do not force any client to implement an interface which is irrelevant to them”. DeliveryProcessing defines placeOrder(), notifySeller(), packageOrder(), deliver(). An online/e-book order does not need packaging or delivery to an address, so the last two methods are irrelevant to it.

Split it into PhysicalDeliveryProcessing (placeOrder, notifySeller, packageOrder, deliver) and OnlineDeliveryProcessing (placeOrder, notifySeller, provideDownloadLink(Product p, String url)). Each client implements only the interface it needs.

One Interface Forces Irrelevant Methods
One Interface Forces Irrelevant Methods
Open image ↗
One Interface Forces Irrelevant Methods
Do Not Force Unnecessary Methods
Do Not Force Unnecessary Methods
Open image ↗
Do Not Force Unnecessary Methods
Interface Segregation: Before and After
Interface Segregation: Before and After
Open image ↗
Interface Segregation: Before and After

2.5 D — Dependency Inversion Principle (DIP)

Prefer abstractions/interfaces over implementations; always associate classes with interfaces rather than actual implementations. A seller should receive payment for products sold. Seller has a BankAccount attribute with number, branch and balance. Adding Paytm, Google Pay or UPI would mean adding more attributes, so Seller is tightly coupled and the code gets complex.

Fix: interface PaymentAccount with payment(); BankAccount, UPI, GooglePay and Paytm implement it; Seller is coupled to PaymentAccount. In main, create the chosen payment object (e.g. BankAccount, later UPI) and pass it to the seller.

Seller Tightly Coupled to BankAccount
Seller Tightly Coupled to BankAccount
Open image ↗
Seller Tightly Coupled to BankAccount
Every New Payment Option Changes Seller
Every New Payment Option Changes Seller
Open image ↗
Every New Payment Option Changes Seller
Depend on the PaymentAccount Interface
Depend on the PaymentAccount Interface
Open image ↗
Depend on the PaymentAccount Interface
Dependency Inversion in Code
Dependency Inversion in Code
Open image ↗
Dependency Inversion in Code
Remember SOLID
Remember SOLID
Open image ↗
Remember SOLID
Which SOLID Principle?
Which SOLID Principle?
Open image ↗
Which SOLID Principle?

2.6 How the principles differ

SRP asks whether a class has a cohesive job. OCP asks how expected new behavior can be added. LSP asks whether a subtype honors its parent contract. ISP asks whether a client is forced to use irrelevant methods. DIP asks whether important business logic depends on a concrete detail instead of a stable abstraction.

PrincipleMain warning signTypical improvement
SRPOne class changes for unrelated reasonsSeparate responsibilities
OCPAdding a product type means editing the existing Product classProvide extension points
LSPSubclass cannot substitute the base method (e.g. notify needs email)Preserve base contract
ISPClient must implement irrelevant methods (e-book: packageOrder, deliver)Split interfaces by client needs
DIPBusiness code creates a specific providerDepend on an abstraction

Follow these principles to make code more efficient, maintainable and easy to understand; they recur in design patterns.

YOUR STUDY PROGRESS

Finished this section? Save your progress.

Back to top ↑