Aller au contenu

Factory Method - A Design Pattern for Object Creation

Ce contenu n’est pas encore disponible dans votre langue.

The Factory Method 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. It promotes loose coupling by eliminating the need to bind application-specific classes into the code.


The Factory Method pattern defines an interface for creating an object, but lets subclasses decide which class to instantiate. It lets a class defer instantiation to subclasses, enabling greater flexibility in determining which objects need to be created.


UI Component Libraries

Create different UI components based on platform or theme.

Document Processing

Generate different document types (PDF, Word, HTML).

Payment Processing

Handle different payment methods (Credit Card, PayPal, Crypto).

Notification Systems

Send notifications via different channels (Email, SMS, Push).

Advantages ✅Disadvantages ❌
Avoids tight coupling between creator and concrete products.Can lead to many subclasses.
Follows the Single Responsibility Principle.Code can become more complex.
Follows the Open/Closed Principle (open for extension).May be overkill for simple cases.
Easy to introduce new product types.Requires understanding of inheritance.

The pattern consists of four main components:

  1. Product Interface: Defines the interface of objects the factory method creates
  2. Concrete Products: Different implementations of the product interface
  3. Creator: Declares the factory method that returns product objects
  4. Concrete Creators: Override the factory method to return different product types

// Product Interface
interface Transport {
deliver(): string;
}
// Concrete Products
class Truck implements Transport {
deliver(): string {
return "Delivering by land in a truck";
}
}
class Ship implements Transport {
deliver(): string {
return "Delivering by sea in a ship";
}
}
class Plane implements Transport {
deliver(): string {
return "Delivering by air in a plane";
}
}
// Creator
abstract class Logistics {
abstract createTransport(): Transport;
planDelivery(): string {
const transport = this.createTransport();
return transport.deliver();
}
}
// Concrete Creators
class RoadLogistics extends Logistics {
createTransport(): Transport {
return new Truck();
}
}
class SeaLogistics extends Logistics {
createTransport(): Transport {
return new Ship();
}
}
class AirLogistics extends Logistics {
createTransport(): Transport {
return new Plane();
}
}
// Usage
function clientCode(logistics: Logistics) {
console.log(logistics.planDelivery());
}
clientCode(new RoadLogistics()); // "Delivering by land in a truck"
clientCode(new SeaLogistics()); // "Delivering by sea in a ship"
clientCode(new AirLogistics()); // "Delivering by air in a plane"

components/ButtonFactory.ts
import { Component } from 'vue';
import PrimaryButton from './PrimaryButton.vue';
import SecondaryButton from './SecondaryButton.vue';
import DangerButton from './DangerButton.vue';
export type ButtonType = 'primary' | 'secondary' | 'danger';
export class ButtonFactory {
static createButton(type: ButtonType): Component {
switch (type) {
case 'primary':
return PrimaryButton;
case 'secondary':
return SecondaryButton;
case 'danger':
return DangerButton;
default:
return PrimaryButton;
}
}
}
<script setup lang="ts">
import { computed } from 'vue';
import { ButtonFactory, ButtonType } from './ButtonFactory';
const props = defineProps<{
type: ButtonType;
}>();
const ButtonComponent = computed(() => ButtonFactory.createButton(props.type));
</script>
<template>
<component :is="ButtonComponent">
<slot />
</component>
</template>

// Product Interface
interface Notification {
send(message: string, recipient: string): void;
}
// Concrete Products
class EmailNotification implements Notification {
send(message: string, recipient: string): void {
console.log(`Sending email to ${recipient}: ${message}`);
}
}
class SMSNotification implements Notification {
send(message: string, recipient: string): void {
console.log(`Sending SMS to ${recipient}: ${message}`);
}
}
class PushNotification implements Notification {
send(message: string, recipient: string): void {
console.log(`Sending push notification to ${recipient}: ${message}`);
}
}
// Creator
abstract class NotificationService {
abstract createNotification(): Notification;
notify(message: string, recipient: string): void {
const notification = this.createNotification();
notification.send(message, recipient);
}
}
// Concrete Creators
class EmailService extends NotificationService {
createNotification(): Notification {
return new EmailNotification();
}
}
class SMSService extends NotificationService {
createNotification(): Notification {
return new SMSNotification();
}
}
class PushService extends NotificationService {
createNotification(): Notification {
return new PushNotification();
}
}
// Usage
const emailService = new EmailService();
emailService.notify("Welcome!", "user@example.com");
const smsService = new SMSService();
smsService.notify("Your code is 1234", "+1234567890");

  1. Identify Common Interface Define a common interface that all products will implement.
  2. Create Concrete Products Implement different versions of the product interface.
  3. Define Creator Class Create an abstract creator class with a factory method.
  4. Implement Concrete Creators Create subclasses that override the factory method.
  5. Use Polymorphism Work with creators through their common interface.

Keep Products Simple

Ensure all products implement the same interface consistently.

Use Meaningful Names

Name your factories and products clearly to indicate their purpose.

Consider Simple Factory First

For simple cases, a single factory class might be sufficient.

Document Dependencies

Clearly document what each concrete creator produces.



Factory MethodSimple Factory
Uses inheritance and subclassesUses a single factory class
More flexible and extensibleSimpler but less flexible
Follows Open / Closed PrincipleViolates Open / Closed Principle
Better for complex hierarchiesBetter for simple cases

-When you don’t know beforehand the exact types of objects your code will work with

  • When you want to provide a way to extend internal components
  • When you want to save system resources by reusing existing objects
  • When the creation process is complex or requires configuration

-When object creation is simple and straightforward

  • When you have only one product type
  • When the added complexity doesn’t provide value
  • When a simple function or constructor would suffice