Design Patterns in Java: Complete Guide with Examples
Design patterns are proven solutions to typical problems that arise during software development. In Java, these patterns are especially in demand due to the language's strict object-oriented paradigm. Using patterns makes code more flexible, maintainable, and scalable.
In this article, we will examine three main groups of design patterns in Java: creational, structural, and behavioral. Each pattern will be accompanied by a practical code example.
Creational Patterns
Creational patterns manage the process of creating objects, making the system independent of how they are constructed. Let's look at two of the most popular patterns: Singleton and Factory Method.
Singleton
Singleton guarantees that a class has only one instance and provides a global access point to it. This pattern is often used for database connections, logging, or caching.
Example of a thread-safe Singleton implementation in Java:
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("Connecting to " + connectionString); }}Usage:
DatabaseConnection db = DatabaseConnection.getInstance("jdbc:mysql://localhost:3306/mydb");db.connect();Factory Method
Factory Method defines an interface for creating an object, but lets subclasses alter the type of object that will be created. This allows delegating instance creation to child classes.
Example of a factory method for creating different types of notifications:
interface Notification { void send(String message);}
class EmailNotification implements Notification { public void send(String message) { System.out.println("Sending email: " + message); }}
class SMSNotification implements Notification { public void send(String message) { System.out.println("Sending SMS: " + message); }}
abstract class NotificationFactory { public abstract Notification createNotification();
public void notifyUser(String message) { Notification notification = createNotification(); notification.send(message); }}
class EmailNotificationFactory extends NotificationFactory { public Notification createNotification() { return new EmailNotification(); }}
class SMSNotificationFactory extends NotificationFactory { public Notification createNotification() { return new SMSNotification(); }}Usage:
NotificationFactory factory = new EmailNotificationFactory();factory.notifyUser("Your order has been confirmed!");Structural Patterns
Structural patterns describe ways to combine classes and objects to form larger structures. Let's look at the Adapter pattern.
Adapter
Adapter allows objects with incompatible interfaces to work together. It wraps one object, providing the required interface for the client.
Example of an adapter for integrating an old logging system