Creational design patterns focus on the process of object creation. They aim to decouple the object creation logic from the rest of the code, making the code more flexible, maintainable, and reusable. By using these patterns, we can control how objects are created, when they are created, and how many of them are created. This is especially important in large - scale applications where object creation can be complex and resource - intensive.
The Singleton pattern ensures that a class has only one instance and provides a global point of access to it.
// Eager initialization
class SingletonEager {
private static final SingletonEager INSTANCE = new SingletonEager();
private SingletonEager() {}
public static SingletonEager getInstance() {
return INSTANCE;
}
}
// Lazy initialization with double - checked locking
class SingletonLazy {
private static volatile SingletonLazy INSTANCE;
private SingletonLazy() {}
public static SingletonLazy getInstance() {
if (INSTANCE == null) {
synchronized (SingletonLazy.class) {
if (INSTANCE == null) {
INSTANCE = new SingletonLazy();
}
}
}
return INSTANCE;
}
}
The Factory Method pattern defines an interface for creating an object, but lets subclasses decide which class to instantiate.
// Product interface
interface Shape {
void draw();
}
// Concrete products
class Circle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a circle");
}
}
class Square implements Shape {
@Override
public void draw() {
System.out.println("Drawing a square");
}
}
// Creator interface
interface ShapeFactory {
Shape createShape();
}
// Concrete creators
class CircleFactory implements ShapeFactory {
@Override
public Shape createShape() {
return new Circle();
}
}
class SquareFactory implements ShapeFactory {
@Override
public Shape createShape() {
return new Square();
}
}
The Abstract Factory pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes.
// Abstract product interfaces
interface Button {
void paint();
}
interface Checkbox {
void paint();
}
// Concrete products for Windows
class WindowsButton implements Button {
@Override
public void paint() {
System.out.println("Windows button painted");
}
}
class WindowsCheckbox implements Checkbox {
@Override
public void paint() {
System.out.println("Windows checkbox painted");
}
}
// Concrete products for Mac
class MacButton implements Button {
@Override
public void paint() {
System.out.println("Mac button painted");
}
}
class MacCheckbox implements Checkbox {
@Override
public void paint() {
System.out.println("Mac checkbox painted");
}
}
// Abstract factory interface
interface GUIFactory {
Button createButton();
Checkbox createCheckbox();
}
// Concrete factories
class WindowsFactory implements GUIFactory {
@Override
public Button createButton() {
return new WindowsButton();
}
@Override
public Checkbox createCheckbox() {
return new WindowsCheckbox();
}
}
class MacFactory implements GUIFactory {
@Override
public Button createButton() {
return new MacButton();
}
@Override
public Checkbox createCheckbox() {
return new MacCheckbox();
}
}
The Builder pattern separates the construction of a complex object from its representation so that the same construction process can create different representations.
class Computer {
private String cpu;
private String ram;
private String storage;
private Computer(ComputerBuilder builder) {
this.cpu = builder.cpu;
this.ram = builder.ram;
this.storage = builder.storage;
}
public static class ComputerBuilder {
private String cpu;
private String ram;
private String storage;
public ComputerBuilder setCpu(String cpu) {
this.cpu = cpu;
return this;
}
public ComputerBuilder setRam(String ram) {
this.ram = ram;
return this;
}
public ComputerBuilder setStorage(String storage) {
this.storage = storage;
return this;
}
public Computer build() {
return new Computer(this);
}
}
@Override
public String toString() {
return "Computer{" +
"cpu='" + cpu + '\'' +
", ram='" + ram + '\'' +
", storage='" + storage + '\'' +
'}';
}
}
The Prototype pattern allows creating new objects by copying an existing object, known as the prototype.
import java.util.HashMap;
import java.util.Map;
// Prototype interface
interface ShapePrototype extends Cloneable {
ShapePrototype clone();
void draw();
}
// Concrete prototype
class CirclePrototype implements ShapePrototype {
@Override
public ShapePrototype clone() {
try {
return (CirclePrototype) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
@Override
public void draw() {
System.out.println("Drawing a circle prototype");
}
}
// Prototype manager
class ShapeCache {
private static final Map<String, ShapePrototype> shapeMap = new HashMap<>();
public static ShapePrototype getShape(String shapeId) {
ShapePrototype cachedShape = shapeMap.get(shapeId);
return cachedShape.clone();
}
public static void loadCache() {
CirclePrototype circle = new CirclePrototype();
shapeMap.put("Circle", circle);
}
}
Creational design patterns are powerful tools in Java development. They provide solutions to common object creation problems, making the code more modular, maintainable, and flexible. By understanding the fundamental concepts, different types of patterns, and following the best practices, developers can effectively use these patterns in their projects. Whether it’s creating a single instance of a class, creating families of related objects, or building complex objects step - by - step, creational design patterns have got you covered.