Skip to content

8.3. Factory Pattern

The Factory Pattern is a creational design pattern that provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created.

Simple Factory Pattern

java
// Product interface
interface Shape {
    void draw();
}

// Concrete products
class Circle implements Shape {
    @Override
    public void draw() {
        System.out.println("Drawing Circle");
    }
}

class Rectangle implements Shape {
    @Override
    public void draw() {
        System.out.println("Drawing Rectangle");
    }
}

class Triangle implements Shape {
    @Override
    public void draw() {
        System.out.println("Drawing Triangle");
    }
}

// Simple Factory
class ShapeFactory {
    public Shape createShape(String shapeType) {
        return switch (shapeType.toLowerCase()) {
            case "circle" -> new Circle();
            case "rectangle" -> new Rectangle();
            case "triangle" -> new Triangle();
            default -> throw new IllegalArgumentException("Unknown shape type: " + shapeType);
        };
    }
}

Factory Method Pattern

java
// Creator abstract class
abstract class Dialog {
    public void render() {
        Button button = createButton();
        button.render();
        button.onClick();
    }

    // Factory method
    public abstract Button createButton();
}

// Concrete creators
class WindowsDialog extends Dialog {
    @Override
    public Button createButton() {
        return new WindowsButton();
    }
}

class WebDialog extends Dialog {
    @Override
    public Button createButton() {
        return new HTMLButton();
    }
}

// Product interface
interface Button {
    void render();
    void onClick();
}

// Concrete products
class WindowsButton implements Button {
    @Override
    public void render() {
        System.out.println("Rendering Windows style button");
    }

    @Override
    public void onClick() {
        System.out.println("Windows button clicked!");
    }
}

class HTMLButton implements Button {
    @Override
    public void render() {
        System.out.println("Rendering HTML button");
    }

    @Override
    public void onClick() {
        System.out.println("HTML button clicked! Navigating to URL...");
    }
}

Abstract Factory Pattern

java
// Abstract Factory
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();
    }
}

// Additional products
interface Checkbox {
    void render();
    void toggle();
}

class WindowsCheckbox implements Checkbox {
    @Override
    public void render() {
        System.out.println("Rendering Windows checkbox");
    }

    @Override
    public void toggle() {
        System.out.println("Windows checkbox toggled");
    }
}

class MacCheckbox implements Checkbox {
    @Override
    public void render() {
        System.out.println("Rendering Mac checkbox");
    }

    @Override
    public void toggle() {
        System.out.println("Mac checkbox toggled");
    }
}

// Client code
class Application {
    private Button button;
    private Checkbox checkbox;

    public Application(GUIFactory factory) {
        button = factory.createButton();
        checkbox = factory.createCheckbox();
    }

    public void render() {
        button.render();
        checkbox.render();
    }
}

Practical Factory Pattern Example

java
// Database connection factory
interface DatabaseConnection {
    void connect();
    void disconnect();
    void executeQuery(String query);
}

class MySQLConnection implements DatabaseConnection {
    @Override
    public void connect() {
        System.out.println("Connecting to MySQL database...");
    }

    @Override
    public void disconnect() {
        System.out.println("Disconnecting from MySQL database...");
    }

    @Override
    public void executeQuery(String query) {
        System.out.println("Executing MySQL query: " + query);
    }
}

class PostgreSQLConnection implements DatabaseConnection {
    @Override
    public void connect() {
        System.out.println("Connecting to PostgreSQL database...");
    }

    @Override
    public void disconnect() {
        System.out.println("Disconnecting from PostgreSQL database...");
    }

    @Override
    public void executeQuery(String query) {
        System.out.println("Executing PostgreSQL query: " + query);
    }
}

class OracleConnection implements DatabaseConnection {
    @Override
    public void connect() {
        System.out.println("Connecting to Oracle database...");
    }

    @Override
    public void disconnect() {
        System.out.println("Disconnecting from Oracle database...");
    }

    @Override
    public void executeQuery(String query) {
        System.out.println("Executing Oracle query: " + query);
    }
}

// Connection Factory
class DatabaseConnectionFactory {
    public static DatabaseConnection createConnection(String dbType) {
        return switch (dbType.toLowerCase()) {
            case "mysql" -> new MySQLConnection();
            case "postgresql" -> new PostgreSQLConnection();
            case "oracle" -> new OracleConnection();
            default -> throw new IllegalArgumentException("Unsupported database type: " + dbType);
        };
    }

    // Factory method with configuration
    public static DatabaseConnection createConnectionFromConfig() {
        // In real application, read from configuration file
        String dbType = System.getProperty("database.type", "mysql");
        return createConnection(dbType);
    }
}

// Usage
public class DatabaseDemo {
    public static void main(String[] args) {
        DatabaseConnection connection = DatabaseConnectionFactory.createConnection("mysql");
        connection.connect();
        connection.executeQuery("SELECT * FROM users");
        connection.disconnect();
    }
}

Factory Pattern Benefits

  1. Loose coupling between client and products
  2. Single Responsibility Principle - creation logic in one place
  3. Open/Closed Principle - easy to introduce new products
  4. Code maintainability - centralized object creation

When to Use Factory Pattern

  1. When you don't know exact types of objects beforehand
  2. When you want to provide extension points for subclasses
  3. When you need to decouple object creation from usage
  4. When you have complex object creation logic