Fundamentals 34 min read

Mastering Java Design Patterns and SOLID Principles: A Practical Guide

This comprehensive tutorial explores the origins, concepts, classifications, and practical applications of software design patterns—including the classic 23 GoF patterns—and explains the seven SOLID principles with clear Java code examples, helping developers write more reusable, readable, and maintainable code.

Intelligent Backend & Architecture
Intelligent Backend & Architecture
Intelligent Backend & Architecture
Mastering Java Design Patterns and SOLID Principles: A Practical Guide

Java design patterns are classic object‑oriented design solutions and code organization techniques widely used in Java frameworks such as Spring, MyBatis, JDBC, and the JVM. Mastering these patterns is an essential skill for senior Java developers.

Background of Software Design Patterns

The term "design pattern" originally came from architecture. In 1977 Christopher Alexander described 253 patterns for towns, neighborhoods, houses, gardens, and rooms in his book A Pattern Language . His 1979 follow‑up The Timeless Way of Building reinforced the idea. In 1987 Kent Beck and Ward Cunningham first applied Alexander’s pattern thinking to Smalltalk GUI generation, but it did not attract much attention until 1990 when the software‑engineering community began discussing design patterns. The milestone came in 1995 when Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides (the "Gang of Four") published Design Patterns: Elements of Reusable Object‑Oriented Software , cataloguing 23 classic patterns that still form the core of modern software design.

Concept and Significance of Software Design Patterns

A software design pattern is a repeatedly used, well‑documented solution to a common design problem. It captures proven experience, offering a reusable template that improves code reusability, readability, and reliability.

Why Learn Design Patterns?

Enhances programmers' thinking, coding, and design abilities.

Standardises design, makes development more engineering‑oriented, and dramatically improves development efficiency, shortening project cycles.

Increases code reusability, readability, reliability, flexibility, and maintainability.

In practice, patterns should be chosen according to the characteristics and requirements of the specific application. For simple programs a straightforward algorithm may be easier than introducing a pattern, but for large projects or framework design, patterns provide clear organisational benefits.

Basic Elements of a Software Design Pattern

A pattern typically includes the following elements: pattern name, alias, motivation, problem, solution, consequences, structure, participants, collaborations, implementation, applicability, known uses, examples, pattern extensions, and related patterns. The four most critical elements are name, problem, solution, and consequences.

1. Pattern Name

The name is a concise term that reflects the problem, characteristics, solution, function, or effect of the pattern, helping people understand and discuss it.

2. Problem

The problem describes the context in which the pattern applies, explaining why the issue occurs and what preconditions must be satisfied.

3. Solution

The solution outlines the participating components, their relationships, responsibilities, and collaborations. Because a pattern is a template, the solution is abstract rather than a concrete implementation.

4. Consequences (Effect)

This part discusses the trade‑offs of applying the pattern, such as impacts on time, space, flexibility, extensibility, portability, and implementation complexity.

Classification of Design Patterns

1. By Purpose

Creational patterns – describe how to create objects (e.g., Singleton, Prototype, Factory Method, Abstract Factory, Builder).

Structural patterns – describe how to compose classes or objects into larger structures (e.g., Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy).

Behavioral patterns – describe how objects cooperate to accomplish tasks (e.g., Template Method, Strategy, Command, Chain of Responsibility, State, Observer, Mediator, Iterator, Visitor, Memento, Interpreter).

2. By Scope

Patterns can be class‑level (static, determined at compile time) or object‑level (dynamic, determined at runtime). For example, Factory Method, Class Adapter, Template Method, and Interpreter are class patterns, while the remaining patterns are object patterns.

GoF 23 Design Patterns Classification Table

Scope\Purpose

Creational

Structural

Behavioral

Class Pattern

Factory Method

Class Adapter

Template Method, Interpreter

Object Pattern

Singleton, Prototype, Abstract Factory, Builder

Proxy, Object Adapter, Bridge, Decorator, Facade, Flyweight, Composite

Strategy, Command, Chain of Responsibility, State, Observer, Mediator, Iterator, Visitor, Memento, Interpreter

Functions of the 23 GoF Design Patterns

Singleton : Ensures a class has only one instance and provides a global access point.

Prototype : Creates new objects by cloning an existing prototype.

Factory Method : Defines an interface for creating a product, letting subclasses decide which class to instantiate.

Abstract Factory : Provides an interface for creating families of related products without specifying concrete classes.

Builder : Separates the construction of a complex object from its representation, allowing the same construction process to create different representations.

Proxy : Controls access to another object, adding protection, caching, or other responsibilities.

Adapter : Converts one interface into another that a client expects.

Bridge : Decouples an abstraction from its implementation so the two can vary independently.

Decorator : Dynamically adds responsibilities to an object without affecting other objects.

Facade : Provides a unified interface to a set of interfaces in a subsystem, making the subsystem easier to use.

Flyweight : Uses sharing to support large numbers of fine‑grained objects efficiently.

Composite : Composes objects into tree structures to represent part‑whole hierarchies.

Template Method : Defines the skeleton of an algorithm, deferring some steps to subclasses.

Strategy : Defines a family of algorithms, encapsulates each one, and makes them interchangeable.

Command : Encapsulates a request as an object, thereby allowing parameterisation of clients with queues, requests, and operations.

Chain of Responsibility : Passes a request along a chain of handlers until one handles it.

State : Allows an object to alter its behaviour when its internal state changes.

Observer : Defines a one‑to‑many dependency so that when one object changes state, all its dependents are notified.

Mediator : Defines an object that encapsulates how a set of objects interact, promoting loose coupling.

Iterator : Provides a way to access the elements of an aggregate object sequentially without exposing its underlying representation.

Visitor : Represents an operation to be performed on the elements of an object structure without changing the classes of the elements.

Memento : Captures and externalises an object's internal state so that the object can be restored later without violating encapsulation.

Interpreter : Defines a representation for a grammar and an interpreter that uses the representation to interpret sentences in the language.

Seven SOLID Principles

1. Single Responsibility Principle (SRP)

A class should have only one reason to change; it should perform only one responsibility.

class Animal {
    public void breathe(String animal) {
        System.out.println(animal + " breathes air");
    }
}

public class Client {
    public static void main(String[] args) {
        Animal animal = new Animal();
        animal.breathe("Cow");
        animal.breathe("Sheep");
        animal.breathe("Pig");
    }
}

When new requirements (e.g., fish breathing water) appear, splitting the class into Terrestrial and Aquatic respects SRP but adds overhead. A quick modification that adds a conditional inside Animal is simpler but violates SRP and introduces risk.

2. Open‑Closed Principle (OCP)

Software entities should be open for extension but closed for modification.

interface Computer {}
class Macbook implements Computer {}
class Surface implements Computer {}

class Factory {
    public Computer produceComputer(String type) {
        if (type.equals("macbook")) return new Macbook();
        else if (type.equals("surface")) return new Surface();
        return null;
    }
}

Refactoring the factory into an interface and concrete factories ( AppleFactory, MSFactory) allows new computer types to be added without changing existing code, satisfying OCP.

3. Liskov Substitution Principle (LSP)

Subtypes must be substitutable for their base types without altering the desirable properties of the program.

class A {
    public int func1(int a, int b) { return a - b; }
}

class B extends A {
    @Override
    public int func1(int a, int b) { return a + b; } // changes behaviour → violates LSP
    public int func2(int a, int b) { return func1(a, b) + 100; }
}

Overriding func1 changes the subtraction behaviour, breaking existing client code. Proper inheritance should extend functionality without altering existing contracts.

4. Dependency Inversion Principle (DIP)

High‑level modules should not depend on low‑level modules; both should depend on abstractions. Abstractions should not depend on details; details should depend on abstractions.

interface IReader { String getContent(); }
class Book implements IReader { public String getContent() { return "Interesting story"; } }
class Newspaper implements IReader { public String getContent() { return "Important news"; } }

class Mother {
    public void say(IReader reader) {
        System.out.println("Mother starts telling a story");
        System.out.println(reader.getContent());
    }
}

Now Mother can work with any IReader implementation, reducing coupling.

5. Interface Segregation Principle (ISP)

Clients should not be forced to depend on interfaces they do not use.

interface I { void method1(); void method2(); void method3(); void method4(); void method5(); }

class B implements I { /* implements all five methods even if only first three are needed */ }
class D implements I { /* implements all five methods even if only 1,4,5 are needed */ }

Splitting I into smaller interfaces ( I1, I2, I3) allows classes to implement only the methods they actually need.

6. Law of Demeter (LOD)

A class should only talk to its immediate friends and not to strangers.

public class Family {
    public void visitPrisoner(Prisoners prisoners) {
        System.out.print("Family says: ");
        prisoners.helpEachOther();
    }
}

public class Prisoners {
    private Inmates inmates = new Inmates();
    public Inmates helpEachOther() {
        System.out.println("Prisoner should help his inmate friend...");
        return inmates;
    }
}

public class Inmates {
    public void weAreFriend() { System.out.println("We are inmates..."); }
}

By letting Family interact only with Prisoners, and letting Prisoners handle the interaction with Inmates, the code respects LOD.

7. Composite/Aggregate Reuse Principle (CRP)

Prefer composition or aggregation over inheritance to achieve code reuse.

Instead of a deep inheritance hierarchy for employee roles, compose a Employee object inside role classes (Manager, Worker, Salesperson), reducing coupling and increasing flexibility.

Memory Mnemonic: "Access with restriction, functions be frugal, dependencies disallowed, dynamics via interfaces, base classes abstract, extensions unchanged."

In practice, aim for low coupling and high cohesion: each class does one thing, interfaces are small, dependencies are inverted, and composition is preferred over inheritance.

With this overview, you should now have a solid foundation of design patterns and the SOLID principles that guide elegant software architecture.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Design PatternsJavaSoftware ArchitectureObject-OrientedSOLID
Intelligent Backend & Architecture
Written by

Intelligent Backend & Architecture

We share personal insights on intelligent, automated backend technologies, along with practical AI knowledge, algorithms, and architecture design, grounded in real business scenarios.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.