Factory Method Pattern – Giải pháp cho việc tạo object mà không phụ thuộc vào class cụ thể

1. Vấn đề

Hãy tưởng tượng bạn đang viết một ứng dụng quản lý vận chuyển. Ban đầu, ứng dụng chỉ cần xử lý vận chuyển đường bộ bằng Truck, nên bạn code trực tiếp như sau:

class Truck {
  deliver() {
    console.log("Giao hàng bằng xe tải");
  }
}

class OrderService {
  createOrder() {
    const transport = new Truck();
    transport.deliver();
  }
}

class ReportService {
  generateReport() {
    const transport = new Truck();
    transport.deliver();
  }
}

Ứng dụng chạy tốt. Nhưng khách hàng yêu cầu thêm Ship. Bạn sửa code như sau:

class OrderService {
  createOrder(config: string) {
    if (config === "road") {
      const transport = new Truck();
      transport.deliver();
    } else if (config === "sea") {
      const transport = new Ship();
      transport.deliver();
    }
  }
}

class ReportService {
  generateReport(config: string) {
    if (config === "road") {
      const transport = new Truck();
      transport.deliver();
    } else if (config === "sea") {
      const transport = new Ship();
      transport.deliver();
    }
  }
}

Vấn đề: if/else bị lặp lại ở nhiều nơi. Nếu ngày mai có thêm Plane, bạn lại phải sửa từng service. Hệ thống càng lớn, càng dễ lỗi.

2. Giải pháp

Factory Method tách biệt việc tạo object khỏi logic nghiệp vụ. Thay vì tạo object trực tiếp bằng new, ta định nghĩa một factory method trong lớp cha (Creator). Các lớp con (Concrete Creator) sẽ quyết định tạo ra loại object cụ thể nào (Concrete Product).

Các thành phần chính

  • Product: interface hoặc abstract class mô tả bản thiết kế hành vi chung.

  • Concrete Product: class cài đặt cụ thể của Product.

  • Creator: lớp khai báo factory method, đồng thời chứa logic nghiệp vụ.

  • Concrete Creator: lớp con override factory method để tạo ra Concrete Product tương ứng.

// Product
interface Transport {
  deliver(): void;
}

// Concrete Products
class Truck implements Transport {
  deliver() { console.log("Giao hàng bằng xe tải"); }
}

class Ship implements Transport {
  deliver() { console.log("Giao hàng bằng tàu biển"); }
}

// Creator
abstract class Logistics {
  abstract createTransport(): Transport;

  planDelivery() {
    const transport = this.createTransport();
    transport.deliver();
  }
}

// Concrete Creators
class RoadLogistics extends Logistics {
  createTransport(): Transport { return new Truck(); }
}

class SeaLogistics extends Logistics {
  createTransport(): Transport { return new Ship(); }
}

// Client code
const logistics: Logistics =
  config === "road" ? new RoadLogistics() : new SeaLogistics();

class OrderService {
  constructor(private logistics: Logistics) {}
  createOrder() {
    this.logistics.planDelivery();
  }
}

class ReportService {
  constructor(private logistics: Logistics) {}
  generateReport() {
    this.logistics.planDelivery();
  }
}

Bây giờ:

  • if/else chỉ dùng một lần duy nhất (chọn Creator).

  • Các service (Order, Report, …) không còn chứa if/else, chỉ làm việc với abstraction Logistics.

  • Muốn thêm PlaneLogistics → chỉ cần viết thêm class mới, không phải sửa service nào.

3. Khi nào nên dùng

  • Khi code của bạn phụ thuộc vào nhiều loại object nhưng không muốn gắn chặt với class cụ thể.

  • Khi bạn cần dễ dàng mở rộng để hỗ trợ loại object mới mà không sửa logic chính.

  • Khi muốn giảm if/else hoặc switch-case liên quan đến việc tạo object.

  • Khi viết framework hoặc thư viện cho phép người dùng tuỳ biến cách tạo object.

4. Ưu và nhược điểm

Ưu điểmNhược điểm
Giảm sự phụ thuộc vào class cụ thể.Code trở nên phức tạp hơn vì có thêm nhiều class con.
Dễ mở rộng theo Open/Closed Principle (mở rộng mà không sửa code cũ).Nếu chỉ có ít loại object thì có thể cảm thấy “dùng dao mổ trâu giết gà”.
Giúp gom logic khởi tạo vào một chỗ duy nhất (dễ bảo trì).Có thể dẫn đến hệ thống class rườm rà.
0
Subscribe to my newsletter

Read articles from Cao Trung Đức directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Cao Trung Đức
Cao Trung Đức

Thợ code học làm kinh tế