Trong các bài trước chúng ta tìm hiểu về Adapter Pattern và Bridge Pattern rồi. Trong bài viết này chúng ta tiếp tục tìm hiểu 1 mẫu thiết kế khác là Composite Pattern với ngôn ngữ PHP. Đây cũng là 1 mẫu thiết kế trong nhóm mẫu thiết kế cấu trúc – structuaral pattern
Mẫu thiết kế Composite như đã nói là một mẫu thiết kế cấu trúc. Mẫu này cho phép bạn kết hợp các đối tượng thành cấu trúc dạng cây và làm việc/tương tác với đối tượng này như là một đối tượng riêng lẻ.
Composite Pattern sẽ phát huy tắc dụng khi chúng ta cần xử lý 1 nhóm đối tượng như cách xử lý 1 object. Nghĩa là coi nhóm đối tượng đó chỉ là 1 đối tượng gồm nhiều đối tượng con mà thôi.
Xem thêm về Composite Pattern trên wiki
Chú ý rằng mẫu thiết kế này chỉ có tác dụng khi mô hình cốt lõi ứng dụng của bạn có thể mô tả được dưới dạng cây.
Hãy tưởng tượng rằng bạn đang có 2 đối tượng: Hộp đựng và sản phẩm (Box, Product). Hộp có thể chứa 1 vài sản phẩm và 1 vài hộp nhỏ hơn. Trong các hộp nhỏ hơn này lại chứa 1 vài sản phẩm, thậm chí là 1 vài hộp nho nhỏ khác nữa
Ví dụ này bạn sẽ thực sự hiểu khi mua hàng trên trang TMĐT. Bạn mua đâu đó tầm 10 món đồ: Sách, điện thoại, bàn chải đánh răng, bút…
Thì bạn sẽ nhận được hộp lớn, trong đó sẽ có hóa đơn, các hộp nhỏ chứa đồ khác. Rồi tiếp đến hộp điện thoại, trong đó lại có hộp nho nhỏ nữa chứa tai nghe.
Và bài toán đặt ra là cần kiểm tra lại tiền từng mặt hàng 1. (Mặc định rằng mỗi mặt hàng đều có tem dán giá riêng). Bạn sẽ phải mở tất cả các hộp này, từ hộp to đến hộp nhỏ, và cộng giá lần lượt lại.
Nghe chừng rất dễ, nhưng đó là trong thực tế. Còn trong bài toán lập trình, các đoạn mã không thể hiểu được Hộp, sản phẩm mà chúng cần duyệt qua nếu không định nghĩa. Và đương nhiên cũng chẳng thể phân biệt được các thứ đó giống khác nhau như thế nào để tính tổng.
Composite Pattern sinh ra để giải quyết vấn đề chúng ta vừa nêu ra.
Theo Composite Pattern, chúng ta cần có 1 interface mà ở đó khai báo các hàm chức năng dùng chung. Cụ thể trong trường hợp này là tính giá tiền (calcPrice). Sau đó các class Product và Box sẽ phải implement chính interface này.
Đối với sản phẩm (Product), hàm này đơn giản là trả về giá sản phẩm đó.
Còn đói với Hộp (Box), hàm này sẽ là tổng các sản phẩm/hộp (product và các box) bên trong nó. (Mức giá này có thể tính thêm 1 số chi phí như phí đóng gói)
Điều này rất tuyệt vời, bởi vì khí các Box và Product đều implement 1 interface như nhau. Chúng ta không cần phân biệt đó là Product hay Box để tính giá khác nhau. Mà đơn giản hơn, lấy giá của đối tượng hiện tại mà thôi.
Trong thực tế việc quản lý phân cấp theo dạng cây này tồn tại khá nhiều nơi.
Có thể kể đến như việc quản lý nhân sự trong 1 công ty lớn. Công ty chia theo từng chi nhánh, cơ sở, phòng ban, tổ… Sau đó cuối cùng mới là từng thành viên/nhân viên trong tổ đó.
Hoặc đơn giản chính là sơ đồ quản lý thư mục trong máy tính. Một thư mục có thể có nhiều thư mục, file con. Trong thư mục con lại có thể chứa tiếp thư mục, file khác…. Cứ liên tục như vậy, thật rất khó để xác định khi nào là kết thúc. Nhưng chúng ta biết được rằng. Hệ thống đó chỉ cấu thành từ 2 loại: 1 là Thư Mục, 2 là Tệp tin.
Một Composite Pattern sẽ bao gồm các phần chính như sau
+ Client: đại diện cho class, hàm nơi mà sẽ làm việc trực tiếp với các đối tượng trong Composite
+ Component: Đây là Interface định nghĩa là phương thức chung cho các đối tượng tham gia vào mẫu thiết kế này
+ Leaf: là đơn vị nhỏ nhất trong Composite, đồng thời cũng là 1 thể hiện của Component
+ Composite: là 1 thể hiện của Component, trong Composite chứa nhiều đơn vị nhỏ hơn là Leaf
Chúng ta sẽ tiến hành cài đặt Composite Pattern cho ví dụ quản lý thư mục bên trên.
Nhiệm vụ của chúng ta sẽ phải tính tổng kích cỡ của Thư mục tổng đó. Ta có lược đồ như sau
Trong này ta sẽ có
Leaf: Tương ứng với các File
Composite: Tương ứng với các folder
Đoạn mã tương ứng như sau
<?php interface Component{ public function showProperties():void; public function totalSize():int; } class File implements Component{ protected $name; protected $size; public function __construct($name,$size){ $this->name = $name; $this->size = $size; } public function showProperties():void{ printf('Tên: %s,Kích cỡ: %s'.PHP_EOL,$this->name,$this->size); } public function totalSize():int{ return $this->size; } } class Folder implements Component{ protected $files = []; public function __construct(array $files){ $this->files = $files; } public function showProperties():void{ foreach($this->files as $file){ $file->showProperties(); } } public function totalSize():int{ $total =0; foreach($this->files as $file){ $total += $file->totalSize(); } return $total; } } $file1 = new File('File 1',10); $file2 = new File('File 2',20); $file3 = new File('File 3',30); $files = [$file1,$file2,$file3]; $folder = new Folder($files); $folder->showProperties(); echo $folder->totalSize();
Kết quả
Tên: File 1,Kích cỡ: 10 Tên: File 2,Kích cỡ: 20 Tên: File 3,Kích cỡ: 30 60
+ Kết hợp với đa hình và đệ quy, bạn có thể dễ dàng làm việc với cấu trúc cây phức tạp.
+ Bạn có thể thêm mới các class, đối tượng mới vào cây hệ thống mà không phá vỡ cấu trúc hiện có của nó, điều này thỏa mãn với nguyên tắc đóng mở trong Nguyên lý SOLID
Nhược điểm là, việc tạo ra 1 interface sử dụng chung không phải điều dễ dàng gì. Trong 1 số trường hợp thì số lượng hàm xử lý trong interface sẽ khá nhiều. Vô hình chung việc này làm cho ứng dụng của bạn trở nên khó hiểu hơn.
+ Composite Pattern chỉ nên được áp dụng khi nhóm đối tượng phải hoạt động như một đối tượng duy nhất (theo cùng một cách).
+ Composite Pattern có thể được sử dụng để tạo ra một cấu trúc giống như cấu trúc cây.
Bạn có thể tham khảo thêm 1 số pattern khác như Adapter Pattern và Bridge Pattern trong PHP
Hi vọng với bài viết Composite Pattern trong PHP này sẽ giúp các bạn hiểu rõ hơn về mẫu thiết kế này cũng như ứng dụng chúng vào các bài toán thực tế 1 cách hiệu quả. Nếu thấy bài viết bổ ích, đừng tiếc 1 like cho bọn mình, và share để mọi người cùng nhưng hoàn thiện kiến thức bạn nhé.
Bình luận: