Các nhà phát triển đang xây dựng ứng dụng Laravel đang được khuyến khích loại bỏ từ khóa new bên trong các controller và chuyển sang sử dụng pattern Factory Method. Bằng cách đưa việc khởi tạo đối tượng vào các factory chuyên dụng, mã nguồn sẽ trở nên dễ mở rộng, kiểm thử và bảo trì hơn—những lợi ích quan trọng khi dự án phát triển vượt quá một vài endpoint.

Tại sao từ khóa new là một rủi ro tiềm ẩn

Khi một controller chứa một dòng mã như

$processor = new StripePaymentProcessor($config);

controller đó hiện đang bị gắn kết chặt chẽ (tightly coupled) với lớp StripePaymentProcessor. Mọi nơi cần một bộ xử lý thanh toán đều lặp lại dòng mã đó, làm phân tán logic khởi tạo khắp mã nguồn. Nếu sau này bộ xử lý yêu cầu thêm một logger, một trình quản lý cache, hoặc một định dạng cấu hình khác, mỗi vị trí đó đều phải được cập nhật. Kết quả là mã nguồn trở nên mong manh, khó thay đổi và khó có thể mock trong các bài kiểm thử đơn vị (unit tests).

Pattern Factory Method giải quyết vấn đề này

Pattern Factory Method thay thế việc khởi tạo trực tiếp bằng một phương thức trả về một instance của một interface cho trước. Controller sẽ phụ thuộc vào interface, trong khi một lớp factory sẽ biết cần xây dựng lớp cụ thể (concrete class) nào và cách lắp ráp các dependency của nó. Toàn bộ logic khởi tạo nằm ở một nơi duy nhất, vì vậy việc thêm một dependency mới hoặc thay đổi các implementation chỉ cần tác động vào factory.

Các lợi thế cốt lõi

  • Loose coupling (Liên kết lỏng lẻo) – Các controller làm việc với các trừu tượng (abstractions), không phải các lớp cụ thể.
  • Centralized construction (Khởi tạo tập trung) – Thay đổi quy trình xây dựng chỉ yêu cầu chỉnh sửa một tệp duy nhất.
  • Testability (Khả năng kiểm thử) – Các factory có thể được stub hoặc thay thế bằng các mock, cho phép kiểm thử controller một cách độc lập.
  • Open/Closed Principle (Nguyên lý Đóng/Mở) – Các tính năng mới (ví dụ: một cổng thanh toán mới) có thể được thêm vào mà không cần chạm đến mã nguồn controller hiện có.

Một ví dụ ẩn dụ về quán cà phê

Hãy tưởng tượng một barista phải tự tay xay hạt, đánh sữa và rót nước cho mỗi đơn hàng bằng một chuỗi dài các câu lệnh if-else. Nếu công thức latte thay đổi, mọi barista đều phải học lại các bước. Một chiếc máy pha cà phê nhận loại đơn hàng và tự xử lý việc chuẩn bị bên trong sẽ giải quyết được vấn đề này: barista chỉ cần báo cho máy biết cần gì, và máy sẽ đóng gói (encapsulate) tất cả các bước. Việc cập nhật công thức bây giờ chỉ đơn giản là điều chỉnh máy pha, chứ không phải mọi barista.

Laravel vốn đã dựa vào các factory

Các thành phần cốt lõi của chính Laravel là minh chứng cho pattern này đang hoạt động:

  • Database – ConnectionFactory quyết định xem nên xây dựng kết nối MySQL hay PostgreSQL.
  • Queues – QueueManager tạo ra các driver cho Redis, SQS hoặc các back-end khác.
  • Filesystem – FilesystemManager tạo ra các instance cho disk local hoặc S3.
  • Mail – MailManager giải quyết các driver SMTP, Mailgun hoặc các driver mail khác.

Nếu framework đã tin tưởng vào các factory cho các dịch vụ quan trọng này, thì mã nguồn tùy chỉnh cũng nên làm theo.

Khi nào nên áp dụng factory

Sử dụng factory khi:

  • Việc khởi tạo đối tượng bao gồm nhiều bước cấu hình hoặc các dịch vụ bên ngoài.
  • Có nhiều implementation có thể thay thế cho nhau (các cổng thanh toán khác nhau, các nhà cung cấp lưu trữ, v.v.).
  • Danh sách các implementation khả thi dự kiến sẽ tăng lên.

Tránh sử dụng factory khi:

  • Việc khởi tạo chỉ là một lời gọi new duy nhất, không thay đổi và không có thiết lập bổ sung.
  • Chỉ có một implementation duy nhất được sử dụng, khiến việc thêm trừu tượng hóa trở thành một sự dư thừa không cần thiết.

Hướng dẫn nhanh: refactor một payment controller

  1. Định nghĩa một interface – PaymentProcessorInterface với phương thức process(array $data).
  2. Triển khai các lớp cụ thể – StripePaymentProcessor, PayPalPaymentProcessor, mỗi lớp đều thực thi interface đó.
  3. Tạo một factory – PaymentProcessorFactory với phương thức make(string $driver): PaymentProcessorInterface. Bên trong, sử dụng switch hoặc một map để trả về lớp phù hợp, đồng thời inject các service cần thiết từ service container của Laravel.
  4. Inject factory – Trong constructor của controller, hãy type-hint PaymentProcessorFactory. Laravel sẽ tự động resolve nó.
  5. Sử dụng factory – $processor = $this->processorFactory->make('stripe'); $processor->process($request->all());

Giờ đây, controller không bao giờ nhắc đến new hay một processor cụ thể nào nữa. Việc thêm một cổng thanh toán mới chỉ đơn giản là tạo một lớp mới và mở rộng map của factory—không cần thay đổi gì trong controller.

Những nhược điểm tiềm ẩn và cách giảm thiểu chúng

Lời chỉ trích chính đối với factory là việc tạo thêm sự gián tiếp, điều này có thể gây cảm giác rườm rà đối với các đối tượng đơn giản. Việc thiết kế quá mức (over-engineering) một service đơn giản có thể làm phình to mã nguồn mà không mang lại lợi ích thực sự. Chìa khóa là đánh giá độ phức tạp của quá trình khởi tạo: nếu một đối tượng chỉ là một vật chứa dữ liệu thuần túy và không có các phụ thuộc (dependencies), việc sử dụng new trực tiếp có thể được chấp nhận. Ngoài ra, các factory có thể trở thành nơi chứa đựng những logic không liên quan. Việc giữ cho các factory tập trung vào việc khởi tạo đối tượng và ủy thác việc cấu hình cho các service provider chuyên dụng sẽ giúp duy trì sự rõ ràng.

Những điều cần lưu ý tiếp theo

  • Service container bindings – Container của Laravel có thể tự động resolve các factory nếu bạn bind interface với class factory trong một service provider.
  • Auto-discovery – Một số package cung cấp các factory riêng của chúng; hãy lưu ý về sự xung đột tên gọi (naming collisions).
  • Testing strategies – Khi unit test các controller, hãy thay thế factory bằng một mock trả về một processor đã được stub, đảm bảo các bài test luôn nhanh chóng và độc lập.

Kết luận

Thay thế các lời gọi new rải rác bằng một factory được đặt đúng chỗ sẽ giúp tập trung hóa việc khởi tạo, giảm sự phụ thuộc (coupling) và giúp mã nguồn tùy chỉnh đồng nhất với triết lý thiết kế của Laravel. Đối với các đội ngũ dự kiến sẽ mở rộng—như thêm các nhà cung cấp thanh toán mới, các backend lưu trữ, hoặc bất kỳ thành phần dạng plug-in nào—mẫu Factory Method là một khoản đầu tư ít chi phí nhưng mang lại hiệu quả cao về tính linh hoạt và sự tự tin. Bản thân bạn trong tương lai, và bất kỳ ai kế thừa mã nguồn này, sẽ cảm ơn bạn.