Design Patterns in Java with Examples: Complete Guide

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Design Patterns in Java: From Theory to Practice



Design Patterns are proven solutions to typical problems in software development. For a Java developer, knowing patterns is not just an advantage, but a necessity. They allow you to write flexible, maintainable, and scalable code. In this article, we will break down four key patterns from the GoF (Gang of Four) catalog with real-world examples in Java.



Why are patterns important for a Java developer?



Java is a strictly typed object-oriented language where patterns help to:

  • Reduce code coupling — classes depend on abstractions, not concrete implementations.
  • Increase reusability — ready-made solutions are easily adapted to new tasks.
  • Simplify teamwork — patterns are a common language for developers.


1. Singleton



The Singleton pattern guarantees that a class has only one instance and provides a global access point to it. In Java, the classic implementation looks like this:



public class DatabaseConnection {    private static volatile DatabaseConnection instance;    private String connectionString;

private DatabaseConnection(String connectionString) { this.connectionString = connectionString; }

public static DatabaseConnection getInstance(String connectionString) { if (instance == null) { synchronized (DatabaseConnection.class) { if (instance == null) { instance = new DatabaseConnection(connectionString); } } } return instance; }

public void connect() { System.out.println("Connected to: " + connectionString); }}


When to use Singleton?

  • Managing a database connection.
  • Loggers and configuration managers.
  • Thread Pool.


Important: In modern Java applications, Singleton is often replaced by dependency injection (DI containers, Spring).



2. Factory Method



Factory Method defines an interface for creating an object but lets subclasses decide which class to instantiate. Let's look at an example of creating notifications:



// Product interfaceinterface Notification {    void send(String message);}

// Concrete productsclass EmailNotification implements Notification { @Override public void send(String message) { System.out.println("Sending Email: " + message); }}

class SMSNotification implements Notification { @Override public void send(String message) { System.out.println("Sending SMS: " + message); }}

// Factoryabstract class NotificationFactory { public abstract Notification createNotification();

public void notifyUser(String message) { Notification notification = createNotification(); notification.send(message); }}

class EmailNotificationFactory extends NotificationFactory { @Override public Notification createNotification() { return new EmailNotification(); }}

class SMSNotificationFactory extends NotificationFactory { @Override public Notification createNotification() { return new SMSNotification(); }}

// Usagepublic class Main { public static void main(String[] args) { NotificationFactory factory = new EmailNotificationFactory(); factory.notifyUser("Hello, User!"); }}


Advantages of Factory Method:

  • Code does not depend on concrete classes.
  • Adding new notification types does not require changing existing code (Open/Closed principle).
  • Simplifies unit testing.

Blogs

Book Recommendations