Tìm hiểu về Abstract Factory Pattern và các ứng dụng?

Abstract Factory Pattern là gì

Trong bài viết trước, chúng ta đã cùng nhau tìm hiểu về loại design pattern đầu tiên mang tên Factory Method Pattern. Trong bài viết này, chúng ta hãy cùng nhau tìm hiểu về một người anh em khác của Factory Method Pattern mang tên ‘Abstract Factory Pattern’ nhé!

Abstract Factory Pattern là gì thế nhỉ?

Tương tự như Factory Method Pattern, Abstract Factory Pattern cũng là một design pattern thuộc nhóm khởi tạo (creational patterns) tuy nhiên nó cao cấp hơn Factory Method Pattern ở chỗ nó cho phép tạo ra một super factory dùng để tạo ra các factory khác.

Để dễ hình dung hơn, hãy tưởng tượng Abstract Factory như là một nhà máy lớn chứa nhiều nhà máy nhỏ, trong các nhà máy đó có những xưởng sản xuất, các xưởng đó tạo ra những sản phẩm khác nhau.

Abstract Factory Pattern

Abstract Factory

Vấn đề đặt ra

Giả sử bạn đang phát triển một ứng dụng phục vụ cho cửa hàng kinh doanh đồ dùng nội thất của mình mang tên FurnitureShopApp. Code hiện tại của bạn đang bao gồm một số class đại diện cho:

  • Nhóm các sản phẩm đi kèm với nhau, ví dụ: Chair + Sofa + CoffeeTable
  • Loại biến thể khác nhau của mỗi nhóm sản phẩm. Ví dụ, cùng nhóm sản phẩm Chair + Sofa + CoffeeTable nhưng lại có nhiều loại biến thể khác nhau, như: Modern, Victorian, ArtDeco.

Một số vấn đề đặt ra

Tuy nhiên trong thời gian gần đây bạn liên tục nhận được những phản hồi không mấy tích cực từ phía khách hàng. Nguyên nhân là do những sản phẩm mà họ nhận được sau khi đặt mua đều không hề ăn khớp và tương thích với nhau.

Ghế sofa kiểu hiện đại không hề ăn khớp và tương thích với ghế bành kiểu Victoria

Ghế sofa kiểu hiện đại không hề ăn khớp và tương thích với ghế bành kiểu Victoria

Là người trực tiếp phát triển ứng dụng, bạn không thể bình chân như vại trước tình hình không mấy tích cực hiện tại. Bạn muốn tìm ra cách nào đó để có thể tạo ra các món đồ nội thất riêng lẻ mà vẫn đảm bảo rằng chúng ăn khớp với các món đồ nội thất còn lại trong cùng nhóm sản phẩm tương ứng. Ngoài ra, vì các đối tác cung cấp đồ nội thất của cửa hàng đều cập nhật danh mục các sản phẩm của họ rất thường xuyên nên bạn cũng muốn tìm ra cách nào đó để có thể hạn chế tối thiểu việc phải thường xuyên quay lại thay đổi các đoạn code cốt lõi mỗi khi những lần cập nhật danh mục này xảy ra.

Giải pháp

Rất may, Abstract Factory Pattern chính là chiếc phao có thể giải nguy cho bạn trong trường hợp này. Điều đầu tiên mà Abstract Factory Pattern đề xuất chính là việc khai báo từng interface cho từng sản phẩm riêng biệt trong một nhóm sản phẩm (ví dụ: Chair, Sofa, CoffeeTable,…). Sau đó, mỗi loại biến thể của từng sản phẩm riêng biệt sẽ phải tiến hành implements interface đó. Ví dụ, tất cả các loại biến thể của Chair phải tiến hành implements interface Chair, tất cả các loại biến thể của Sofa phải tiến hành implements interface Sofa,…

Abstract Factory Pattern chính là giải pháp cho bạn

Tiếp theo các bạn cần khai báo một Abstract Factory – một interface bao gồm các factory method cho từng sản phẩm riêng biệt trong một nhóm sản phẩm (ví dụ: createdChair, createdSofa và createdCoffeeTable,…). Mỗi factory method này sẽ trả về kiểu dữ liệu ứng với loại interface đã được khai báo của nó (ví dụ: Chair, Sofa, CoffeeTable,…)

Vậy còn nhóm các sản phẩm ứng với mỗi loại biến thể thì sao? Chúng ta sẽ tạo ra các factory riêng biệt dựa trên interface AbstractFactory đã được khai báo, mỗi factory này sẽ lần lượt implement các factory method của AbstractFactory interface và trả về tất cả các sản phẩm của một nhóm cụ thể, ví dụ: ModernFactory chỉ có thể tạo các đối tượng ModernChair, ModernSofa và ModernCoffeeTable.

Với cài đặt như trên, giờ đây khi cần nhóm sản phẩm thuộc loại biến thể nào đó, phía khách hàng chỉ cần chỉ ra factory tương ứng là đã có thể nhận được đúng các sản phẩm đảm bảo ăn khớp và tương thích với nhau. Thật tuyệt đúng không nào.

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

Abstract Factory Pattern bao gồm năm thành phần cơ bản là: Abstract Factory, Concrete Factory, Abstract Product, Concrete ProductClient.

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

Trong đó:

  • AbstractFactory: Khai báo dạng interface hoặc abstract class chứa các phương thức để tạo ra các đối tượng abstract.
  • ConcreteFactory: Xây dựng, cài đặt các phương thức tạo các đối tượng cụ thể.
  • AbstractProduct: Khai báo dạng interface hoặc abstract class để định nghĩa đối tượng abstract.
  • ConcreteProduct: Cài đặt của các đối tượng cụ thể, cài đặt các phương thức được quy định tại AbstractProduct.
  • Client: là đối tượng sử dụng AbstractFactory và các AbstractProduct.

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

Nói lý thuyết vậy là đủ rồi, cùng thử áp dụng Abstract Factory Pattern để giải quyết vấn đề đã đặt ra nhé.

Đầu tiên chúng ta sẽ tạo ra 3 interface tương ứng với 3 sản phẩm khác nhau là Chair, Sofa CoffeeTable.

<?php
       interface Chair {
             public function hasLegs(): string;
             public function sitOn(): string;
       }

       interface Sofa {
             public function hasLegs(): string;
             public function sitOn(): string;
       }

       interface CoffeeTable {
             public function hasLegs(): string;
             public function putOn(): string;
       }
?>

Sau khi tạo xong các interface trên, chúng ta tiến hành tạo ra các class ứng với mỗi loại biến thể của từng sản phẩm, mỗi class này sẽ phải implements interface tương ứng của nó.

<?php
       class VictorianChair implements Chair {
             public function hasLegs(): string {
                   return 'Victorian Chair has four long legs.'
             }

             public function sitOn(): string {
                   return 'Sit on Victorian Chair.';
             }
       }

       class ArtDecoChair implements Chair {
             public function hasLegs(): string {
                   return 'Art Deco Chair has four short legs.';
             }

             public function sitOn(): string {
                   return 'Sit on Art Deco Chair.';
             }
       }

       class ModernChair implements Chair {
             public function hasLegs(): string {
                   return 'Modern Chair has no legs.';
             }

             public function sitOn(): string {
                   return 'Sit on Modern Chair.';
             }
       }

       class VictorianSofa implements Sofa {
             public function hasLegs(): string {
                   return 'Victorian Sofa has four long legs.';
             }
 
             public function sitOn(): string {
                   return 'Sit on Victorian Sofa.';
             }
       }

       class ArtDecoSofa implements Sofa {
             public function hasLegs(): string {
                   return 'Art Deco Sofa has four short legs.';
             }

             public function sitOn(): string {
                   return 'Sit on Art Deco Sofa.';
             }
       }

       class ModernSofa implements Sofa {
             public function hasLegs(): string {
                   return 'Modern Sofa has no legs.';
             }

             public function sitOn(): string {
                   return 'Sit on Modern Sofa.';
             }
       }

       class VictorianCoffeeTable implements CoffeeTable {
             public function hasLegs(): string {
                   return 'Victorian Coffee Table has four long legs.';
             }

             public function putOn(): string {
                   return 'Put something on Victorian Coffee Table.';
             }
       }

       class ArtDecoCoffeeTable implements CoffeeTable {
             public function hasLegs(): string {
                   return 'Art Deco Coffee Table has only one leg.';
             }

             public function putOn(): string {
                   return 'Put something on Art Deco Table.';
             }
       }

       class ModernCoffeeTable implements CoffeeTable {
             public function hasLegs(): string {
                   return 'Modern Coffee Table has four short legs.';
             }
 
             public function putOn(): string {
                   return 'Put something on Modern Coffee Table.';
             }
       }
?>

Tiếp theo, chúng ta sẽ tiến hành tạo ra FurnitureFactory – một interface bao gồm các factory method cho từng sản phẩm riêng biệt trong một nhóm sản phẩm. Mỗi factory method này sẽ trả về kiểu dữ liệu ứng với loại interface đã được khai báo của nó.

<?php
       interface FurnitureFactory {
             public function createChair(): Chair;
             public function createSofa(): Sofa;
             public function createCoffeeTable(): CoffeeTable;
       }
?>

Tiếp đến, chúng ta tiến hành tạo ra nhóm các sản phẩm ứng với mỗi loại biến thể bằng cách tạo ra các factory riêng biệt dựa trên interface AbstractFactory đã được khai báo, mỗi factory này sẽ lần lượt override các factory method của AbstractFactory interface và trả về tất cả các sản phẩm của một nhóm cụ thể.

<?php
       class VictorianFurnitureFactory {
             public function createChair(): Chair {
                   return new VictorianChair();
             }

             public function createSofa(): Sofa {
                   return new VictorianSofa();
             }

             public function createCoffeeTable(): CoffeeTable {
                   return new VictorianCoffeeTable();
             }
       }

       class ArtDecoFactory {
             public function createChair(): Chair {
                   return new ArtDecoChair();
             }

             public function createSofa(): Sofa {
                   return new ArtDecoSofa();
             }

             public function createCoffeeTable(): CoffeeTable {
                   return new ArtDecoCoffeeTable();
             }
       }

       class ModernFactory {
             public function createChair(): Chair {
                   return new ModernChair();
             }

             public function createSofa(): Sofa {
                   return new ModernSofa();
             }

             public function createCoffeeTable(): CoffeeTable {
                   return new ModernCoffeeTable();
             }
       }
?>

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

function clientCode(FurnitureFactory $factory)
{
      $chair = $factory->createChair();
      $sofa = $factory->createSofa();
      $coffeeTable = $factory->createCoffeeTable();

      echo $chair->hasLegs() . "\n";
      echo $chair->sitOn() . "\n";
      echo $sofa->hasLegs() . "\n";
      echo $sofa->sitOn() . "\n";
      echo $coffeeTable->hasLegs() . "\n";
      echo $coffeeTable->sitOn() . "\n";
}

      echo "* Victorian Furniture Factory\n";
      clientCode(new VictorianFurnitureFactory());

      echo "\n";

      echo "* Art Deco Factory\n";
      clientCode(new ArtDecoFactory());

      echo "\n";

      echo "* Modern Factory\n";
      clientCode(new ModernFactory());

Kết quả thu được:

* Victorian Furniture Factory
Victorian Chair has four long legs.
Sit on Victorian Chair.
Victorian Sofa has four long legs.
Sit on Victorian Sofa.
Victorian Coffee Table has four long legs.
Put something on Victorian Coffee Table.

* Art Deco Factory
Art Deco Chair has four short legs.
Sit on Art Deco Chair.
Art Deco Sofa has four short legs.
Sit on Art Deco Sofa.
Art Deco Coffee Table has only one leg.
Put something on Art Deco Table.

* Modern Factory
Modern Chair has no legs.
Sit on Modern Chair.
Modern Sofa has no legs.
Sit on Modern Sofa.
Modern Coffee Table has four short legs.
Put something on Modern Coffee Table.

Như vậy, chúng ta có thể thấy rằng giờ đây khi cần nhóm sản phẩm thuộc loại biến thể nào đó, phía khách hàng chỉ cần chỉ ra factory tương ứng là đã có thể nhận được đúng các sản phẩm đảm bảo ăn khớp và tương thích với nhau.

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

Ưu điểm

  • Abstract Factory Pattern giúp đảm bảo rằng các product mà bạn nhận được từ một factory đều tương thích với nhau
  • Abstract Factory Pattern giúp hạn chế sự phụ thuộc giữa creator và concrete products.
  • Abstract Factory 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 Abstract Factory 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ề Abstract Factory 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 design pattern mới toanh thuộc nhóm khởi tạo mang tên ‘Builder’. Các bạn nhớ đón xem nhé.

Gửi phản hồi