Creational patterns deal with object creation mechanisms, trying to create objects in a manner suitable to the situation. They provide flexibility in what gets created, who creates it, how it gets created, and when.
Abstract Factory
Problem: Need to create families of related or dependent objects without specifying their concrete classes.
When to use:
- System should be independent of how its products are created
- System should be configured with one of multiple families of products
- Family of related product objects must be used together
- You want to reveal only interfaces, not implementations
Structure:
- Provides an interface for creating families of related objects
- Concrete factories implement the creation methods
- Client uses abstract interfaces only
Example (TypeScript):
// Abstract products
interface Button { render(): void; }
interface Checkbox { render(): void; }
// Abstract factory
interface GUIFactory {
createButton(): Button;
createCheckbox(): Checkbox;
}
// Concrete Windows implementations
class WindowsButton implements Button {
render() { console.log('Rendering Windows button'); }
}
class WindowsCheckbox implements Checkbox {
render() { console.log('Rendering Windows checkbox'); }
}
// Concrete Windows factory
class WindowsFactory implements GUIFactory {
createButton(): Button { return new WindowsButton(); }
createCheckbox(): Checkbox { return new WindowsCheckbox(); }
}
// Concrete macOS implementations
class MacButton implements Button {
render() { console.log('Rendering Mac button'); }
}
class MacCheckbox implements Checkbox {
render() { console.log('Rendering Mac checkbox'); }
}
// Concrete macOS factory
class MacFactory implements GUIFactory {
createButton(): Button { return new MacButton(); }
createCheckbox(): Checkbox { return new MacCheckbox(); }
}
// Client code
class Application {
constructor(private factory: GUIFactory) {}
createUI() {
const button = this.factory.createButton();
const checkbox = this.factory.createCheckbox();
button.render();
checkbox.render();
}
}
// Usage
const os = process.platform;
const factory = os === 'darwin' ? new MacFactory() : new WindowsFactory();
const app = new Application(factory);
app.createUI();Example (Ruby):
# Abstract products
class Button
def render
raise NotImplementedError
end
end
class Checkbox
def render
raise NotImplementedError
end
end
# Abstract factory
class GUIFactory
def create_button
raise NotImplementedError
end
def create_checkbox
raise NotImplementedError
end
end
# Concrete Windows implementations
class WindowsButton < Button
def render
puts 'Rendering Windows button'
end
end
class WindowsCheckbox < Checkbox
def render
puts 'Rendering Windows checkbox'
end
end
# Concrete Windows factory
class WindowsFactory < GUIFactory
def create_button
WindowsButton.new
end
def create_checkbox
WindowsCheckbox.new
end
end
# Usage
factory = WindowsFactory.new
button = factory.create_button
checkbox = factory.create_checkbox
button.render
checkbox.renderBuilder
Problem: Too many constructor parameters make object creation complex and error-prone.
When to use:
- Construction process must allow different representations
- Object has many optional parameters
- Step-by-step construction is needed
- Construction logic is complex
Structure:
- Builder interface defines steps to construct product
- Concrete builders implement these steps
- Director orchestrates the building process (optional)
- Product is the complex object being built
Example (TypeScript):
class Pizza {
size?: string;
cheese?: boolean;
pepperoni?: boolean;
mushrooms?: boolean;
describe() {
console.log(`${this.size} pizza with ${this.cheese ? 'cheese' : 'no cheese'}`);
}
}
class PizzaBuilder {
private pizza: Pizza = new Pizza();
setSize(size: string): this {
this.pizza.size = size;
return this;
}
addCheese(): this {
this.pizza.cheese = true;
return this;
}
addPepperoni(): this {
this.pizza.pepperoni = true;
return this;
}
addMushrooms(): this {
this.pizza.mushrooms = true;
return this;
}
build(): Pizza {
return this.pizza;
}
}
// Usage
const pizza = new PizzaBuilder()
.setSize('large')
.addCheese()
.addPepperoni()
.build();Example (Ruby):
class Pizza
attr_accessor :size, :cheese, :pepperoni, :mushrooms
def describe
puts "#{size} pizza with #{cheese ? 'cheese' : 'no cheese'}"
end
end
class PizzaBuilder
def initialize
@pizza = Pizza.new
end
def size(value)
@pizza.size = value
self
end
def add_cheese
@pizza.cheese = true
self
end
def add_pepperoni
@pizza.pepperoni = true
self
end
def add_mushrooms
@pizza.mushrooms = true
self
end
def build
@pizza
end
end
# Usage
pizza = PizzaBuilder.new
.size('large')
.add_cheese
.add_pepperoni
.buildFactory Method
Problem: Need to defer instantiation to subclasses, letting them decide which class to instantiate.
When to use:
- Class can’t anticipate the type of objects it needs to create
- Class wants its subclasses to specify objects to create
- Classes delegate responsibility to helper subclasses
Structure:
- Creator declares factory method returning product
- Concrete creators override factory method
- Products share common interface
Example (TypeScript):
interface Document {
open(): void;
save(): void;
}
abstract class Application {
abstract createDocument(): Document;
newDocument() {
const doc = this.createDocument();
doc.open();
return doc;
}
}
class PDFDocument implements Document {
open() { console.log('Opening PDF'); }
save() { console.log('Saving PDF'); }
}
class WordDocument implements Document {
open() { console.log('Opening Word doc'); }
save() { console.log('Saving Word doc'); }
}
class PDFApplication extends Application {
createDocument(): Document {
return new PDFDocument();
}
}
class WordApplication extends Application {
createDocument(): Document {
return new WordDocument();
}
}
// Usage
const app: Application = new PDFApplication();
const doc = app.newDocument(); // Opens PDFExample (Ruby):
class Document
def open
raise NotImplementedError
end
def save
raise NotImplementedError
end
end
class Application
def create_document
raise NotImplementedError
end
def new_document
doc = create_document
doc.open
doc
end
end
class PDFDocument < Document
def open
puts 'Opening PDF'
end
def save
puts 'Saving PDF'
end
end
class PDFApplication < Application
def create_document
PDFDocument.new
end
end
# Usage
app = PDFApplication.new
doc = app.new_documentPrototype
Problem: Need to create new objects by copying existing ones, especially when creation is expensive.
When to use:
- Objects to create are specified by prototypical instance
- Classes to instantiate are specified at runtime
- Avoid building parallel class hierarchies of factories
- Instances have few state combinations
Structure:
- Prototype interface declares cloning method
- Concrete prototypes implement cloning
- Client creates new objects by cloning prototype
Example (TypeScript):
interface Cloneable {
clone(): this;
}
class Shape implements Cloneable {
constructor(
public x: number,
public y: number,
public color: string
) {}
clone(): this {
return Object.create(this);
}
}
class Circle extends Shape {
constructor(
x: number,
y: number,
color: string,
public radius: number
) {
super(x, y, color);
}
}
// Usage
const redCircle = new Circle(10, 20, 'red', 5);
const blueCircle = redCircle.clone();
blueCircle.color = 'blue';
blueCircle.x = 30;Example (Ruby):
class Shape
attr_accessor :x, :y, :color
def initialize(x, y, color)
@x = x
@y = y
@color = color
end
def clone
self.dup
end
end
class Circle < Shape
attr_accessor :radius
def initialize(x, y, color, radius)
super(x, y, color)
@radius = radius
end
end
# Usage
red_circle = Circle.new(10, 20, 'red', 5)
blue_circle = red_circle.clone
blue_circle.color = 'blue'
blue_circle.x = 30Singleton
Problem: Need exactly one instance of a class, with global access point.
When to use:
- There must be exactly one instance of a class
- Instance must be accessible from well-known access point
- Sole instance should be extensible by subclassing
Warning: Singletons are often considered an anti-pattern. They introduce global state and make testing difficult. Consider dependency injection instead.
Example (TypeScript):
class Database {
private static instance: Database;
private connection: any;
private constructor() {
// Private constructor prevents direct instantiation
this.connection = this.connect();
}
private connect() {
console.log('Connecting to database...');
return { /* connection object */ };
}
static getInstance(): Database {
if (!Database.instance) {
Database.instance = new Database();
}
return Database.instance;
}
query(sql: string) {
console.log(`Executing: ${sql}`);
}
}
// Usage
const db1 = Database.getInstance();
const db2 = Database.getInstance();
console.log(db1 === db2); // trueExample (Ruby):
require 'singleton'
class Database
include Singleton
def initialize
@connection = connect
end
def connect
puts 'Connecting to database...'
# connection object
end
def query(sql)
puts "Executing: #{sql}"
end
end
# Usage
db1 = Database.instance
db2 = Database.instance
puts db1 == db2 # trueBetter alternative (Dependency Injection):
class Database {
constructor(private config: DbConfig) {
this.connection = this.connect();
}
private connect() { /* ... */ }
query(sql: string) { /* ... */ }
}
// Single instance managed by DI container
const db = new Database(config);
// Inject where needed
class UserRepository {
constructor(private db: Database) {}
findById(id: number) {
return this.db.query(`SELECT * FROM users WHERE id = ${id}`);
}
}
const userRepo = new UserRepository(db);Related Patterns
- Structural Patterns - Often use creational patterns
- Behavioral Patterns - May be created using factories
- Design Patterns Overview