Fading Coder

One Final Commit for the Last Sprint

Home > Notes > Content

Factory Pattern: Creational Design Pattern

Notes 1

Factory Pattern is one of the most commonly used creational design patterns in software development, particularly in Java and C++. It provides a standardized way to create objects while hiding the underlying instantiation logic from client code, and uses a shared interface to reference the newly created objects.

Core Intent

Define an interface for creating an object, but defer the actual instantiation to subclasses, allowing each subclass to determine which concrete object type to create.

Problem Solved

This pattern addresses the challenge of selecting appropriate concrete object implementations without coupling client code to specific product classes. It centralizes object creation logic to reduce code duplication and improve maintainability.

When to Use

Use this pattern when you need to create different object instances based on runtime conditions, or when you do not know the exact concrete classes of objects you need to create ahead of time.

Key Implementation Details

Object creation logic is executed in factory subclasses, with all products implementing a common abstract interface. Clients interact only with this interface and factory methods, never directly with concrete product classes.

Common Use Cases

  1. Vehicle manufacturing: Ordering a vehicle without needing to understand its assembly process
  2. Database abstraction layers: Swapping database backends by adjusting dialect and driver configurations, as seen in Hibernate
  3. Logging frameworks: Supporting multiple logging targets such as local files, system event logs, or remote servers
  4. Protocol handling: Implementing common interfaces for network protocols like POP3, IMAP, and HTTP

Pros and Cons

Advantages

  • Clients only need to know the identifier for the desired object type and its shared interface
  • High extensibility: Adding new product types only requires creating a new concrete product class and updating factory logic
  • Decouples object usage from object creation, adhering to the single responsibility principle
  • Centralizes object creation code to avoid duplication across the codebase

Disadvantages

  • Each new product addition requires creating a new concrete class and modifying factory logic, leading to an increase in total class count and system complexity
  • Overuse for simple objects that can be directly instantiated adds unnecessary overhead

Implementation Examples

C++ Implementation

Step 1: Define Abstract Shape Interface

Create a base abstract class for all shape products. shape_base.h

#pragma once
#include <iostream>

class Shape {
public:
    virtual ~Shape() = default;
    virtual void render() const = 0;
};
Step 2: Create Concrete Shape Classes

Implement the abstract interface for specific shape types. circle_shape.h

#pragma once
#include "shape_base.h"

class CircleShape : public Shape {
public:
    void render() const override {
        std::cout << "Drawing a circle\n";
    }
};

rect_shape.h

#pragma once
#include "shape_base.h"

class RectShape : public Shape {
public:
    void render() const override {
        std::cout << "Drawing a rectangle\n";
    }
};

square_shape.h

#pragma once
#include "shape_base.h"

class SquareShape : public Shape {
public:
    void render() const override {
        std::cout << "Drawing a square\n";
    }
};
Step 3: Build the Shape Factory Class

Create a factory class that generates concrete shape objects based on input type. shape_factory.h

#pragma once
#include "shape_base.h"
#include "circle_shape.h"
#include "rect_shape.h"
#include "square_shape.h"
#include <string>

class ShapeFactoryClass {
public:
    Shape* createShape(const std::string& shapeType) {
        if (shapeType.empty()) {
            return nullptr;
        }
        if (shapeType == "circle") {
            return new CircleShape();
        } else if (shapeType == "rectangle") {
            return new RectShape();
        } else if (shapeType == "square") {
            return new SquareShape();
        }
        return nullptr;
    }
};
Step 4: Implement Client Code

Use the factory to retrieve and interact with shape objects. main.cpp

#include <iostream>
#include "shape_factory.h"
#include <memory>

int main() {
    std::unique_ptr<ShapeFactoryClass> factory = std::make_unique<ShapeFactoryClass>();
    
    std::unique_ptr<Shape> circle = std::unique_ptr<Shape>(factory->createShape("circle"));
    circle->render();
    
    std::unique_ptr<Shape> rect = std::unique_ptr<Shape>(factory->createShape("rectangle"));
    rect->render();
    
    std::unique_ptr<Shape> square = std::unique_ptr<Shape>(factory->createShape("square"));
    square->render();
    
    std::cin.get();
    return 0;
}
Sample Output
Drawing a circle
Drawing a rectangle
Drawing a square

Java Implementation

Step 1: Define the Shape Interface

Shape.java

public interface Shape {
    void draw();
}
Step 2: Create Concrete Shape Classes

Implement the shared interface for each shape type. CircleShape.java

public class CircleShape implements Shape {
    @Override
    public void draw() {
        System.out.println("Rendering circle shape");
    }
}

RectangleShape.java

public class RectangleShape implements Shape {
    @Override
    public void draw() {
        System.out.println("Rendering rectangle shape");
    }
}

SquareShape.java

public class SquareShape implements Shape {
    @Override
    public void draw() {
        System.out.println("Rendering square shape");
    }
}
Step 3: Build the Shape Factory

ShapeFactory.java

public class ShapeFactory {
    public Shape getShapeInstance(String shapeType) {
        if (shapeType == null) {
            return null;
        }
        String normalizedType = shapeType.toLowerCase();
        switch (normalizedType) {
            case "circle":
                return new CircleShape();
            case "rectangle":
                return new RectangleShape();
            case "square":
                return new SquareShape();
            default:
                return null;
        }
    }
}
Step 4: Create Demo Client Class

ShapeFactoryDemo.java

public class ShapeFactoryDemo {
    public static void main(String[] args) {
        ShapeFactory factory = new ShapeFactory();
        
        Shape circle = factory.getShapeInstance("CIRCLE");
        circle.draw();
        
        Shape rectangle = factory.getShapeInstance("RECTANGLE");
        rectangle.draw();
        
        Shape square = factory.getShapeInstance("SQUARE");
        square.draw();
    }
}
Sample Output
Rendering circle shape
Rendering rectangle shape
Rendering square shape

Core Characteristics

  • Classified as a creational design pattern
  • Hides object creation logic from client code
  • Uses a shared interface to standardize references to created objects

Related Articles

Designing Alertmanager Templates for Prometheus Notifications

How to craft Alertmanager templates to format alert messages, improving clarity and presentation. Alertmanager uses Go’s text/template engine with additional helper functions. Alerting rules referenc...

Deploying a Maven Web Application to Tomcat 9 Using the Tomcat Manager

Tomcat 9 does not provide a dedicated Maven plugin. The Tomcat Manager interface, however, is backward-compatible, so the Tomcat 7 Maven Plugin can be used to deploy to Tomcat 9. This guide shows two...

Skipping Errors in MySQL Asynchronous Replication

When a replica halts because the SQL thread encounters an error, you can resume replication by skipping the problematic event(s). Two common approaches are available. Methods to Skip Errors 1) Skip a...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.