JH

SOLID Principles in Object-Oriented Design

Introduction to Complex Software Design

  • Goal: Build complex software that is easy to modify, extend, and maintain.
    • Early approach: Structured programming.
      • Influential paper: “gotos considered harmful”.
    • Modern approach: Object-Oriented Programming (OOP).
      • Language support by itself is insufficient; success depends on how tools are used.
  • Object-Oriented Design (OOD) supplies guidance for organising code and managing dependencies.

Robert C. Martin ("Uncle Bob")

  • Key evangelist for OOD best-practices and agile craftsmanship.
  • Major books (all repeatedly cited in industry):
    • Clean Code: A Handbook of Agile Software Craftsmanship
    • Agile Software Development, Principles, Patterns, and Practices
    • Agile Principles, Patterns, and Practices in C#
    • The Clean Coder: A Code of Conduct for Professional Programmers
  • Blog article that popularised today’s topic: PrinciplesOfOod (butunclebob.com)

The SOLID Acronym (First Five OOD Principles)

  • SRP Single Responsibility Principle
  • OCP Open/Closed Principle
  • LSP Liskov Substitution Principle
  • ISP Interface Segregation Principle
  • DIP Dependency Inversion Principle
  • Motto: “Software development is not a Jenga game”; proper dependency management prevents systems from collapsing when touched.

Single Responsibility Principle (SRP)

  • Definition: “A class should have only one reason to change.”
  • Cohesion: All elements of a module should be functionally related; unrelated forces of change violate SRP.
  • Motivational-poster slogan: “Just Because You Can, Doesn’t Mean You Should.”

Code Walk-Through

  • Initial naïve class (Book)
  public class Book {
      private String name;
      private String author;
      private String text;
      // constructor, getters, setters
  }
  • Mixing unrelated responsibilities (text processing and output formatting):
  void printTextToConsole(String text) { /* formatting + printing */ }
  void printTextToAnotherMedium(String text) { /* e.g., PDF */ }
  • SRP-compliant refactor: split into Book (domain) vs. BookPrinter (presentation).

Additional Domain Example

  • Person and Account separated rather than embedding account logic inside person.
  public class Person { /* identity fields + List<Account> */ }
  public class Account { /* account details */ }

Practical Significance

  • High cohesion → easier unit testing, clearer mental model, lower merge conflicts.
  • SRP violations often manifest as “God classes” or “utility blobs.”

Open/Closed Principle (OCP)

  • Definition: “Software entities (classes, modules, functions) should be open for extension but closed for modification.”
  • Heuristic side-rules:
    • All instance variables \text{should be private}.
    • Avoid global variables.
  • Aim: Ship code that rarely changes yet absorbs new requirements via polymorphism, inheritance, composition, or configuration.
  • Poster: “Open-chest surgery is not needed when putting on a coat.”

Guitar Example

  • Base class:
  public class Guitar { String make; String model; int volume; }
  • Extending behaviour without editing Guitar:
  public class SuperCoolGuitarWithFlames extends Guitar {
      String flameColor;
  }
  • Net effect: New features (flames) delivered while core class remains untouched.

Liskov Substitution Principle (LSP)

  • Definition (Barbara Liskov, 1987): “Objects of a superclass shall be replaceable with objects of a subclass without altering correctness.”
  • Formal phrasing (informal): If A is subtype of B then anywhere B is expected, an instance of A should behave equivalently.
  • Poster: “If it looks like a duck, quacks like a duck, but needs batteries—wrong abstraction.”

Broken Car Example

  • Interface:
  public interface Car { void turnOnEngine(); void accelerate(); }
  • ElectricCar cannot implement turnOnEngine meaningfully → violates LSP by throwing AssertionError.
  • Remedy: split into richer hierarchy, e.g. Vehicle, EnginePoweredVehicle, or apply ISP to segregate “engine” capability.

Consequences

  • Base class should be ignorant of its derivatives; subclasses honour invariants, pre-/post-conditions.
  • Violations cause runtime surprises, type-checking workarounds, and brittle polymorphism.

Interface Segregation Principle (ISP)

  • Definition: “Clients should not be forced to depend upon interfaces they do not use.”
  • Avoid “fat” or “polluted” interfaces that couple unrelated clients.
  • Poster: “You want me to plug this in, where?”

Bear Zoo Example

  • All-in-one interface:
  interface BearKeeper { washTheBear(); feedTheBear(); petTheBear(); }
  • Split per-role:
  interface BearCleaner { washTheBear(); }
  interface BearFeeder  { feedTheBear(); }
  interface BearPetter  { petTheBear(); }
  • Implementations: BearCarer (clean + feed) vs. CrazyPerson (pet only). Each class depends solely on what it needs.

Benefits & Implications

  • Less recompilation, finer access control, easier mocking, safer access to dangerous operations.

Dependency Inversion Principle (DIP)

  • Definition: “Depend on abstractions, not on concretions. High-level modules should not depend on low-level modules; both depend on abstractions.”
  • Poster: “Would you solder a lamp directly to the wiring in a wall?”

Architectural Insight

  • Conventional layering: higher-level (Policy layer) directly calls lower-level (Utility layer) classes → tight coupling.
  • DIP refactors such that:
    \text{High-level Module} \rightarrow \text{Interface}\leftarrow \text{Low-level Module}
    • Abstraction in middle reverses compile-time dependency direction.

Coffee-Machine Example (Stackify article)

  • Interfaces: CoffeeMachine, EspressoMachine.
  • Implementations: BasicCoffeeMachine, PremiumCoffeeMachine.
  • By coding to interfaces, UI or service layer can swap in different machine objects without change; grinders, brewing units & configuration maps live behind abstractions.

Practical Techniques

  • Constructor injection, factory patterns, dependency-injection frameworks (Spring, Guice, Dagger).
  • DIP increases reuse opportunities for higher-level policy code.

Cross-Cutting Observations

  • SOLID principles are synergistic, not isolated rules. Violating one often leads to violations of others (e.g., SRP ↔ ISP, LSP ↔ OCP).
  • These principles are language-agnostic—applicable even without OOP languages via disciplined design (e.g., using modules, pure functions, typeclasses).
  • Ethical / Professional angle (Clean Coder): Engineers have responsibility to keep codebase maintainable for future colleagues.

External Resources & Further Reading

  • Motivational posters collection: http://lostechies.com/derickbailey/2009/02/11/solid-development-principles-in-motivational-pictures/
  • Uncle Bob’s article: http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod
  • Baeldung tutorial: https://www.baeldung.com/solid-principles
  • Stackify DIP case study: https://stackify.com/dependency-inversion-principle/

Key Takeaways for the Exam

  • Recognise each SOLID principle’s formal definition, purpose, and code symptoms when violated.
  • Be ready to illustrate with concise class diagrams or code snippets (e.g., Book/BookPrinter, Car/ElectricCar).
  • Understand interplay: e.g., applying ISP often helps restore LSP; enforcing DIP aids OCP.
  • Remember poster metaphors—they’re handy mnemonic devices for quick recall.