Design Patterns in Java: A Complete Guide with Examples
Design patterns are proven solutions to typical problems that arise during software development. In Java, as a strictly typed object-oriented language, patterns are especially effective. They help make code flexible, reusable, and easy to maintain. In this article, we will break down three main groups of patterns (according to the GoF classification): creational, structural, and behavioral — with real-world examples in Java.
1. Creational Patterns
These patterns manage the object creation process, making the system independent of how objects are created, composed, and represented.
1.1. Singleton
Ensures that a class has only one instance and provides a global point of access to it. A classic example is a database connection pool or a logger.
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); }}
// UsageDatabaseConnection conn = DatabaseConnection.getInstance("jdbc:mysql://localhost:3306/mydb");conn.connect();1.2. Factory Method
Defines an interface for creating an object but lets subclasses alter the type of object that will be created. It is ideal when the exact type of object is not known in advance.
interface Transport { void deliver();}
class Truck implements Transport { public void deliver() { System.out.println("Delivery by truck on land"); }}
class Ship implements Transport { public void deliver() { System.out.println("Delivery by ship by sea"); }}
abstract class Logistics { abstract Transport createTransport(); public void planDelivery() { Transport t = createTransport(); t.deliver(); }}
class RoadLogistics extends Logistics { Transport createTransport() { return new Truck(); }}
class SeaLogistics extends Logistics { Transport createTransport() { return new Ship(); }}
// UsageLogistics logistics = new RoadLogistics();logistics.planDelivery(); // Output: Delivery by truck on land2. Structural Patterns
These are responsible for composing classes and objects, helping to build complex structures from simple components.
2.1. Adapter
Allows objects with incompatible interfaces to work together. It is often used to integrate legacy code with new code.
// Old interfaceinterface OldSystem { String getDataInXML();}
// New interfaceinterface NewSystem { String getDataInJSON();}
// Old system implementationclass LegacySystem implements OldSystem { public String getDataInXML() { return "<data>Hello</data>"; }}
// Adapterclass XMLToJSONAdapter implements NewSystem { private OldSystem oldSystem; public XMLToJSONAdapter(OldSystem oldSystem) { this.oldSystem = oldSystem; } public String getDataInJSON() { String xml = oldSystem.getDataInXML(); return "{\\"data\\": \\"" + xml.replaceAll("<.*?>", "") + "\\"}"; }}
// A