Skip to main content
Factory MethodCreational
Also known asVirtual Constructor

In object-oriented programming (OOP), the factory pattern is a pattern that guides in the creation of objects without specifying their exact classes. Rather than calling a constructor directly you can invoke a factory method to create an object. This allows for more flexibility and decoupling in the code, as the specific class of the object being created can be determined at runtime based on certain conditions or configurations.

🧩The problem

Let's say you have a website that sells toys. When you create your company you think to yourself "I sure love selling toys and will only ever sell large, bulky toys", therefore you ship these in boxes. After a while though it becomes clear that kids really want small-form toys as well. You would love to save some costs on shipping by sending them in envelopes instead of boxes. However because you initially tightly coupled your shipping system to shipping in boxes that it is now quite hard to change this in the present. Sure you can add a case that'll check whether it needs to be send using a box or an envelope, but it'll create quite messy and clunky code. Moreover what'll you do when you decide to add insured boxes or local pick-up, instead of delivering?

🛠️Solutions

The factory method pattern suggests that you create a separate method (the factory method) that is responsible for creating the objects you need. This method can then be overridden in subclasses to create different types of objects based on the specific requirements. This way, it'll become much easier to add new types of shipping methods in the future without having to modify the existing codebase. Instead you can just create a new subclass that implements the factory method to create the new shipping method. As long as all shipping methods adhere to the same interface, the rest of your codebase can remain unchanged, whilst still allowing for additional flexibility and extensibility.

🏛️Metaphors

Imagine a toy factory that produces different types of toys. Instead of having a single assembly line that creates all toys, the factory has multiple specialized assembly lines for different toy types (e.g., action figures, dolls, puzzles). Each assembly line is responsible for creating its specific type of toy. When a customer places an order, the factory uses the appropriate assembly line to produce the requested toy type. This way, the factory can easily add new toy types in the future by simply introducing new assembly lines without disrupting the existing ones.

💡Real-world examples

Common practical scenarios for applying the Factory Method pattern include:

  • A logistics application that needs to create different types of transport vehicles (e.g., trucks, ships, planes) based on the cargo being shipped. Here all vehicles adhere to a common 'vehicle' (interface) type.
  • A document generation system that produces different types of documents (e.g., invoices, reports, letters) based on user input. They still all rely on one 'type' (interface) of document.

⚖️ Pros and Cons

Pros

  • Avoids tightly coupling code to specific classes
  • Enhances code flexibility and extensibility
  • Promotes adherence to the Open/Closed Principle

Cons

  • Increased complexity due to the introduction of additional classes
  • Potential for over-engineering in simple scenarios

🔍Applicability

  • 💡
    Use the Factory Method pattern when a class cannot anticipate the type of objects it needs to create beforehand.
    The factory method allows subclasses to determine the specific type of object to instantiate. Meaning in the future if you need a new shipping type you can just create a new subclass, instead of refactoring existing code.
  • 💡
    Use the factory method when you want to re-use resources or manage object creation more efficiently.
    The factory method allows you to centralize the object creation logic, making it easier to manage and optimize resource usage. For example you can implement caching or pooling mechanisms within the factory method to reuse existing objects instead of creating new ones each time.

🧭Implementation Plan

To implement a Factory Method manually:

  1. Define a common interface, these should declare methods that all created objects must implement. E.g. ShippingObject with shipProduct() and package() methods, so that all shipping methods adhere to the same interface.
  2. Add an empty factory method to a base class, e.g. createShippingMethod(). The return type should match the common interface defined in step 1.
  3. Create the concrete creator (sub-) classes, e.g. BoxShipping and EnvelopeShipping, that override the factory method to instantiate and return specific types of objects that implement the common interface.
  4. Client code should call the factory method to create objects, instead of directly instantiating them. This way the client code remains decoupled from the specific classes being created.

💻Code samples

// Product interface - defines what all shipping methods must do
interface ShippingMethod {
calculateCost(weight: number): number;
package(item: string): string;
deliver(): string;
}

// Concrete Products - different shipping implementations
class BoxShipping implements ShippingMethod {
calculateCost(weight: number): number {
return weight * 2.5; // $2.50 per kg for box shipping
}

package(item: string): string {
return `${item} securely packed in a protective box with bubble wrap`;
}

deliver(): string {
return "Delivered via ground transport (3-5 business days)";
}
}

// Concrete Products - different shipping implementations
class EnvelopeShipping implements ShippingMethod {
calculateCost(weight: number): number {
return Math.max(5, weight * 1.2); kg
}

package(item: string): string {
return `${item} placed in a padded envelope`;
}

deliver(): string {
return "Delivered via express mail (1-2 business days)";
}
}

// Concrete Products - different shipping implementations
class InsuredShipping implements ShippingMethod {
calculateCost(weight: number): number {
return weight * 3.5 + 15;
}

package(item: string): string {
return `${item} packed in reinforced box with tracking and insurance`;
}

deliver(): string {
return "Delivered with signature required and full insurance coverage";
}
}

// Creator - defines the factory method
abstract class ShippingService {
// Factory method - subclasses decide which shipping method to create
abstract createShippingMethod(): ShippingMethod;

// Template method - uses the factory method
processOrder(item: string, weight: number): void {
const shipping = this.createShippingMethod();

console.log(`📦 ${shipping.package(item)}`);
console.log(`💰 Cost: $${shipping.calculateCost(weight).toFixed(2)}`);
console.log(`🚚 ${shipping.deliver()}`);
}
}

// Concrete Creators - implement the factory method
class StandardShippingService extends ShippingService {
createShippingMethod(): ShippingMethod {
return new BoxShipping();
}
}

class ExpressShippingService extends ShippingService {
createShippingMethod(): ShippingMethod {
return new EnvelopeShipping();
}
}

class PremiumShippingService extends ShippingService {
createShippingMethod(): ShippingMethod {
return new InsuredShipping();
}
}

// Client code - works with any shipping service without knowing the details
function processCustomerOrder(service: ShippingService, item: string, weight: number): void {
service.processOrder(item, weight);
}

// Usage - easily switch between different shipping strategies
const toyOrder = "Limited Edition Action Figure";
const weight = 0.5; // 0.5 kg

processCustomerOrder(new StandardShippingService(), toyOrder, weight);
processCustomerOrder(new ExpressShippingService(), toyOrder, weight);
processCustomerOrder(new PremiumShippingService(), toyOrder, weight);

🎮Playground

note

This sample is to get a 'feel' for the pattern. The code itself may not reflect a correct implementation of the pattern.

Live Editor
function FactoryMethodDemo() {
  // State for the order form and shipping log
  const [item, setItem] = React.useState("Action Figure");
  const [weight, setWeight] = React.useState(0.5);
  const [shippingType, setShippingType] = React.useState("standard");
  const [orders, setOrders] = React.useState([]);

  // Factory functions - each creates a different shipping method
  const createStandardShipping = () => ({
    type: "Standard Box",
    calculateCost: (w) => w * 2.5,
    deliveryTime: "3-5 business days",
    icon: "📦"
  });

  const createExpressShipping = () => ({
    type: "Express Envelope",
    calculateCost: (w) => Math.max(5, w * 1.2),
    deliveryTime: "1-2 business days",
    icon: "✉️"
  });

  const createPremiumShipping = () => ({
    type: "Premium Insured",
    calculateCost: (w) => w * 3.5 + 15,
    deliveryTime: "Next day + signature",
    icon: "🎁"
  });

  // Factory Method - returns the appropriate shipping method
  const getShippingMethod = (type) => {
    switch(type) {
      case "standard": return createStandardShipping();
      case "express": return createExpressShipping();
      case "premium": return createPremiumShipping();
      default: return createStandardShipping();
    }
  };

  // Process order using the factory method
  const handleProcessOrder = () => {
    const shipping = getShippingMethod(shippingType);
    const cost = shipping.calculateCost(weight);
    
    const order = {
      id: Date.now(),
      item,
      weight,
      shipping: shipping.type,
      cost: cost.toFixed(2),
      delivery: shipping.deliveryTime,
      icon: shipping.icon
    };
    
    setOrders([order, ...orders]);
  };

  const handleClearOrders = () => setOrders([]);

  return (
    <div style={{ fontFamily: "sans-serif" }}>
      <h3>Factory Method — Shipping Demo</h3>
      
      {/* Order Form */}
      <div style={{ marginBottom: 16, padding: 12, border: "1px solid #ccc", borderRadius: 4 }}>
        <div style={{ marginBottom: 8 }}>
          <label style={{ display: "block", marginBottom: 4 }}>
            Item:
            <input
              type="text"
              value={item}
              onChange={(e) => setItem(e.target.value)}
              style={{ marginLeft: 8, padding: 4, width: "200px" }}
            />
          </label>
        </div>
        
        <div style={{ marginBottom: 8 }}>
          <label style={{ display: "block", marginBottom: 4 }}>
            Weight (kg):
            <input
              type="number"
              value={weight}
              onChange={(e) => setWeight(parseFloat(e.target.value) || 0)}
              step="0.1"
              min="0.1"
              style={{ marginLeft: 8, padding: 4, width: "100px" }}
            />
          </label>
        </div>
        
        <div style={{ marginBottom: 12 }}>
          <label style={{ display: "block", marginBottom: 4 }}>
            Shipping Method:
            <select
              value={shippingType}
              onChange={(e) => setShippingType(e.target.value)}
              style={{ marginLeft: 8, padding: 4 }}
            >
              <option value="standard">Standard (Box)</option>
              <option value="express">Express (Envelope)</option>
              <option value="premium">Premium (Insured)</option>
            </select>
          </label>
        </div>
        
        <button onClick={handleProcessOrder} style={{ marginRight: 8 }}>
          Process Order
        </button>
        <button onClick={handleClearOrders}>
          Clear Orders
        </button>
      </div>

      {/* Orders Display */}
      <div>
        <h4>Processed Orders:</h4>
        {orders.length === 0 ? (
          <p style={{ color: "#666" }}>No orders yet. Create one above!</p>
        ) : (
          <ul style={{ listStyle: "none", padding: 0 }}>
            {orders.map((order) => (
              <li
                key={order.id}
                style={{
                  marginBottom: 8,
                  padding: 12,
                  border: "1px solid #ddd",
                  borderRadius: 4,
                  backgroundColor: "#f9f9f9"
                }}
              >
                <div style={{ fontSize: "1.2em", marginBottom: 4 }}>
                  {order.icon} <strong>{order.item}</strong> ({order.weight} kg)
                </div>
                <div style={{ fontSize: "0.9em", color: "#555" }}>
                  Method: {order.shipping}<br/>
                  Cost: ${order.cost}<br/>
                  Delivery: {order.delivery}
                </div>
              </li>
            ))}
          </ul>
        )}
      </div>
      
      <div style={{ marginTop: 16, padding: 12, backgroundColor: "#e3f2fd", borderRadius: 4 }}>
        <strong>💡 Factory Method in Action:</strong> Each shipping type is created by a 
        different factory function. The <code>getShippingMethod()</code> decides which factory 
        to use, keeping the order processing code decoupled from specific shipping implementations.
      </div>
    </div>
  );
}
Result
Loading...

🔗Relations to other patterns

  • Lots of people start with the Factory Method pattern prior to implementing something such as Abstract Factory, Builder or Prototype patterns. The latter patterns are more flexible, but also come with increased complexity.
  • Prototype isn't based on inheritance, so it does not have its drawbacks. On the other hand, Prototype requires a complicated initialization of the cloned object. Factory Method is based on inheritance but doesn't require an initialization step.
  • Factory Method is a specialization of Template Method. At the same time, a Factory Method may serve as a step in a large Template Method.

📚Sources

Information used in this page was collected from various reliable sources: