Factory Method Pattern là gì?

Tìm hiểu về Factory Method Pattern

Trong bài viết trước, chúng ta đã có cái nhìn bao quát về định nghĩa của design patterns, phân loại design patterns cũng như vai trò đặc biệt quan trọng của chúng trong quy trình phát triển phần mềm. Trong bài viết này, chúng ta sẽ bắt tay vào tìm hiểu design pattern đầu tiên cũng là một trong số những design pattern được sử dụng phổ biến nhất mang tên “Factory Method Pattern”.

Factory Method Pattern là gì thế?

Factory Method Pattern (hay còn được gọi với cái tên ngắn gọn hơn là Factory Pattern) là một design pattern thuộc nhóm khởi tạo (Creational patterns). Pattern này được sinh ra nhằm mục đích khởi tạo một đối tượng mới mà không cần thiết phải chỉ ra một cách chính xác class nào sẽ được khởi tạo. Factory Method Pattern giải quyết vấn đề này bằng cách định nghĩa một factory method cho việc tạo đối tượng, và các lớp con thừa kế có thể override phương thức này để chỉ rõ đối tượng nào sẽ được khởi tạo.

Vấn đề đặt ra

Giả sử bạn là người trực tiếp phát triển ứng dụng quản lý logistics mang tên LogisticsApp. Phiên bản đầu tiên của ứng dụng này mới chỉ giới hạn ở việc quản lý vận tải hàng hóa bằng đường bộ thông qua hệ thống xe tải chuyên dụng (Truck) của các đối tác. Chính vì thế, phần lớn code xử lý hiện tại đều có sự gắn kết nào đó với class Truck.

Sau một thời gian hoạt động, ứng dụng của bạn ngày càng được đánh giá cao và được các công ty vận tải biển chú ý. Họ liên tục gửi lời mời hợp tác trong đó yêu cầu bổ sung thêm hình thức vận tải bằng đường biển thông qua hệ thống tàu chuyên dụng (Ship) của họ vào ứng dụng này của bạn.

Là người trực tiếp phát triển ứng dụng từ những ngày đầu, bạn cảm thấy rất vui mừng khi đứa con tinh thần của mình giờ đây đã đạt được những thành công nhất định. Thế nhưng khi bắt đầu suy nghĩ về việc làm thế nào để tích hợp các đoạn code xử lý liên quan đến hình thức vận tải đường biển vào codebase hiện có, bạn mới chợt nhận ra có khá nhiều vấn đề cần phải giải quyết.

Vấn đề lớn nhất được đặt ra

Đó là việc hiện tại phần lớn code của ứng dụng đều có sự gắn kết nào đó với class Truck mà bạn đã tạo ra. Để thêm class Ship bắt buộc bạn phải thay đổi hầu như toàn bộ code đã có trước đó. Chưa kể, nếu có yêu cầu bổ sung thêm một số class mới toanh như Plane hay Train trong tương lai chắc chắn bạn sẽ cần phải thực hiện lại tất cả những thay đổi này. Nếu không biết cách tổ chức code hợp lí, code của bạn sẽ ngày càng trở nên rối rắm và đánh đố hơn bao giờ hết.

Factory Method Pattern

Hiện tại, phần lớn code của ứng dụng đều có sự gắn kết nào đó với class Truck

Giải pháp

Cái khó ló cái khôn =D Rất may đã có một giải pháp giúp bạn giải quyết vấn đề nêu trên, đó là sử dụng Factory Method Pattern. Như đã nói, pattern này giúp thay thế việc khởi tạo đối tượng một cách trực tiếp (sử dụng toán tử new) bằng việc gọi đến một factory method. Về thực chất, toán tử new vẫn được sử dụng để tạo ra đối tượng mới, nhưng việc thực hiện lời gọi này là do phương thức kia đảm nhận. Các đối tượng mới được tạo ra nhờ factory method còn được gọi là các “product”.

Thoạt nhìn, sự thay thế này có vẻ khá vô nghĩa: có vẻ như chúng ta chỉ đang di chuyển lời gọi new một đối tượng mới từ nơi này sang nơi khác trong code mà thôi. Tuy nhiên, nếu xem xét kĩ bạn sẽ thấy rằng giờ đây chúng ta có thể override factory method này để chỉ rõ đối tượng nào sẽ được khởi tạo.

Cấu trúc của Factory Method Pattern

Cấu trúc tổng quan của Factory Method Pattern bao gồm 4 thành phần chính là: Product, concrete product(s), creator và concrete creator(s).

Cấu trúc tổng quan của Factory Method Pattern

Trong đó:

  • Product là một interface chung mà tất cả các product con đều phải implements.
  • Mỗi concrete product là một product con cụ thể implements từ interface Product.
  • Class Creator là nơi khai báo factory method có kiểu dữ liệu trả về là Product cho phép trả về các product mới.
  • Mỗi Concrete Creator là một class kế thừa từ Creator có nhiệm vụ override factory method và trả về loại product tương ứng.

Một lưu ý nhỏ, factory method không nhất thiết phải luôn luôn tạo ra các đối tượng mới. Đôi khi nó cũng có thể trả về các đối tượng đã được tạo từ trước đó và đang được lưu trữ trong cache, object pool hoặc từ các nguồn khác.

Nên sử dụng Factory Method Pattern khi nào nhỉ?

Factory Method Pattern phát huy được ưu điểm của nó trong một số trường hợp sau:

  • Khi bạn chưa biết nên khởi tạo đối tượng mới từ class nào.
  • Khi bạn muốn tập trung các đoạn code liên quan đến việc khởi tạo các đối tượng mới về cùng một nơi để dễ thao tác và xử lý.
  • Khi bạn không muốn người dùng phải biết hết tên của các class có liên quan đến quá trình khởi tạo cũng như muốn che giấu, đóng gói toàn bộ logic của quá trình khởi tạo một đối tượng mới nào đó khỏi phía người dùng.

Giải quyết vấn đề đã đặt ra

Cùng thử áp dụng Factory Method Pattern để giải quyết vấn đề đã đặt ra nhé =D

Đầu tiên, chúng ta nhận thấy rằng cả Truck và Ship đều có những điểm chung sau:

  • Cùng là các phương tiện
  • Cùng vận chuyển hàng hóa

Do đó chúng ta sẽ tạo ra interface Transport, interface này chứa phương thức deliver() bắt buộc bất kì class nào implements từ nó cũng đều phải override lại.

<?php
                  interface Transport 
                  {
                        public function deliver(): string;
                  }
?>

Tiếp theo chúng ta sẽ tiếp tục tạo ra 2 concrete product class là Truck và Ship. Cả 2 class này đều cùng implements interface Transport ở trên, do đó chúng đều phải override lại phương thức deliver():

<?php
class Truck implements Transport 
{
      public function deliver(): string
      {
            // Truck vận chuyển hàng bên trong các thùng và bằng đường bộ 
            return “Truck: Deliver by land in a box.”;
      }
}


class Ship implements Transport 
{
      public function deliver(): string
      {
            // Ship vận chuyển hàng bên trong các container và bằng đường biển
            return “Ship: Deliver by sea in a container.”;
      }
}
?>

Factory Method Pattern

Tiếp đến, chúng ta sẽ tạo ra creator class mang tên Logistics, nhiệm vụ chính của class này là cung cấp phương thức abstract mang tên createTransport cho phép trả về một đối tượng có kiểu dữ liệu là Transport. Nội dung của phương thức này sẽ do các concrete creator class định nghĩa chi tiết.

<?php

abstract class Logistics

{
          public function planDelivery(string $transportType): string
          {
                   $transport = $this->createTransport($transportType);
                   echo $transport->deliver();
          } 
          abstract public function createTransport(string $transporType): Transport;
}
?>

Chúng ta tiếp tục tạo ra 2 concrete creator class là RoadLogisticsSeaLogistics với nội dung như sau:

<?php
class RoadLogistics extends Logistics
{
        public function createTransport(string $transportType)
        {
                  if(strtolower($transportType) === strtolower(“Truck”)) {
                          return new Truck;

                  }

        }

}
class SeaLogistics extends Logistics {

        public function createTransport(string $transportType)

        {
                  if(strtolower($transportType) === strtolower(“Ship”)) {

                          return new Ship;
                  }
        }
}
?>

Vậy là phần cài đặt đã xong, cùng chạy thử xem kết quả như thế nào nhé! =D

$roadLogistics = new RoadLogistics;
$seaLogistics = new SeaLogistics;

$transport = $roadLogistics->planDelivery(“Truck”);
$transport->delivery();

echo “\n”;

$transport = $seaLogistics->planDelivery(“Ship”);
$transport->delivery();

Kết quả thu được:

Truck: Deliver by land in a box.
Ship: Deliver by sea in a container.

Giờ đây thay vì tương tác trực tiếp với các class đại diện cho một loại phương tiện cụ thể, ứng dụng chỉ tương tác với các đối tượng có kiểu dữ liệu là Transport. Loại hình vận chuyển (RoadLogistics, SeaLogistics) sẽ quyết định đến kiểu dữ liệu cụ thể của Transport sẽ là gì. Nhờ vậy, giờ đây chúng ta có thể thoải mái bổ sung thêm các loại phương tiện vận chuyển mới vào cho ứng dụng mà không sợ làm thay đổi logic của code đã có. Tuyệt vời =D

Ưu và nhược điểm của Factory Method Pattern

Ưu điểm

  • Factory Method Pattern giúp hạn chế sự phụ thuộc giữa creator và concrete products.
  • Factory Method Pattern giúp gom các đoạn code tạo ra product vào một nơi trong chương trình, nhờ đó giúp dễ theo dõi và thao tác.
  • Với Factory Method Pattern, chúng ta có thể thoải mái thêm nhiều loại product mới vào chương trình mà không làm thay đổi các đoạn code nền tảng đã có trước đó.

Nhược điểm

  • Code có thể trở nên nhiều hơn và phức tạp hơn do đòi hỏi phải sử dụng nhiều class mới có thể cài đặt được pattern này.

Kết luận

Trong bài viết này, chúng ta đã cùng nhau tìm hiểu về Factory Method Pattern, hy vọng pattern này sẽ giúp ích cho các bạn trong tương lai. Trong bài viết tiếp theo của series, mình sẽ tiếp tục giới thiệu cho các bạn một người anh em cùng cha khác ông nội của Factory Method Pattern là Abstract Factory Pattern. Các bạn nhớ đón xem nhé =D

Gửi phản hồi