2.3. Composite

2.3.1. تعریف

دیزاین پترن Composite یک الگوی ساختاری محسوب میشه که به شما اجازه میده آبجکت ها رو در ساختارهای درختی با هم ترکیب کنید و با این ساختار ها به گونه ای کار کنید که گویا آبجکت های مستقل از هم هستند.

این الگو بر مبنای برخورد یکسان با آبجکت های مستقل و ترکیبی از آبجکت ها بنا شده.

2.3.2. اجزاء

الگوی طراحی Composite از چند بخش اصلی تشکیل میشه:

  1. Component: یک interface کلی که تمام آبجکت های حاضر در ترکیب سلسله مراتب اون رو پیاده سازی می کنن.

2. Leaf: عضو پایه ای تشکیل دهنده ترکیب سلسله مراتب که امکان داشتن فرزند رو نداره و Component رو پیاده سازی می کنه و نمایش دهنده ی آبجکت های مستقل موجود در ترکیب هست.

3. Composite: این آبجکتی هست که آبجکت های Leaf رو به عنوان فرزندان خودش داره، Component رو پیاده سازی می کنه و behavior فرزندانش رو تعریف می کنه. همچنین میتونه متدهای اضافه ای داشته باشه برای manipulate فرزندان.

UML of Composite Design Pattern

NGiacomo Ritucci CC BY-SA 3.0, via Wikimedia Commons

برای اینکه این اجزاء رو بهتر درک کنید فرض کنید یک سیستم Ordering داریم که تشکیل شده از یک سری محصول و جعبه.

در بالاترین سطح این سلسله مراتب ما Order رو داریم و در سطوح پایین تر جعبه (Composite) و همینطور در سطوح آخر محصول (Leaf) رو داریم.

2.3.3. چه زمانی استفاده میشه؟

این الگو رو زمانی استفاده می کنیم که مطمئن بشیم امکان ایجاد و تصویر یک ساختار درختی سلسله مراتبی در برنامه و هسته ی اون وجود داره.

اولین چیزی که میتونیم تصویر کنیم ساختار DOM یک فایل HTML هست.

Caution

✅ مزایای استفاده

امکان کار ساده تر با ساختارهای درختی پیچیده

رعایت اصل Open/Closed به علت عدم تغییر ماهیت و ساختار کد با اضافه شدن المان ها به ساختار درختی

Warning

❌ معایب استفاده

تعریف یک interface واحد برای کلاس هایی که عملکرد متفاوتی دارن معمولا کار سختی هست

2.3.4. کاربرد عملی

تصور کنید یک شرکت داریم که هم محصولات فیزیکی ارائه می کنه و هم محصولات دیجیتال.

محصولات فیزیکی می تونن کتاب و گجت باشن و محصولات دیجیتال میتونن فیلم یا موسیقی باشن.

به نظر میرسه چون با یک ساختار درختی مواجه هستیم می تونیم از دیزاین پترن Composite استفاده کنیم.

2.3.5. پیاده سازی

ابتدا میریم سراغ Component که شامل یک interface برای تعریف متدها و پراپرتی های مشترک هست:

1چ<?php
2
3// Component interface
4interface Product
5{
6    public function getName(): string;
7
8    public function getPrice(): float;
9}

هر محصول شامل نام و قیمت هست.

بعد از این کلاس Composite رو تعریف می کنیم که اینجا کاتالوگ محصولات نام داره و میتونه شامل آرایه ای از محصولات باشه ولی در عین حال interface بالا رو هم پیاده سازی می کنه:

 1<?php
 2
 3// Composite class
 4class ProductCatalog implements Product
 5{
 6    private array $products = [];
 7
 8    public function addProduct(Product $product)
 9    {
10        $this->products[] = $product;
11    }
12
13    public function getName(): string
14    {
15        return "Product Catalog";
16    }
17
18    public function getPrice(): float
19    {
20        $totalPrice = 0;
21
22        foreach ($this->products as $product) {
23            $totalPrice += $product->getPrice();
24        }
25
26        return $totalPrice;
27    }
28}

نامش که مشخصه ولی قیمتش میشه شامل مجموع قیمت محصولاتی که در کاتالوگ وجود داره.

و بعد هم که تعریف Leaf یا لایه های آخر این سلسله مراتب رو داریم که میشه همون انواع محصولات واقعی که سیستم ما ارائه می کنه:

 1<?php
 2
 3// Leaf classes
 4class Book implements Product
 5{
 6    private string $name;
 7    private float $price;
 8
 9    public function __construct(string $name, float $price)
10    {
11        $this->name = $name;
12        $this->price = $price;
13    }
14
15    public function getName(): string
16    {
17        return $this->name;
18    }
19
20    public function getPrice(): float
21    {
22        return $this->price;
23    }
24}
25
26class Gadget implements Product
27{
28    private string $name;
29    private float $price;
30
31    public function __construct(string $name, float $price)
32    {
33        $this->name = $name;
34        $this->price = $price;
35    }
36
37    public function getName(): string
38    {
39        return $this->name;
40    }
41
42    public function getPrice(): float
43    {
44        return $this->price;
45    }
46}
47
48class Movie implements Product
49{
50    private string $name;
51    private float $price;
52
53    public function __construct(string $name, float $price)
54    {
55        $this->name = $name;
56        $this->price = $price;
57    }
58
59    public function getName(): string
60    {
61        return $this->name;
62    }
63
64    public function getPrice(): float
65    {
66        return $this->price;
67    }
68}
69
70class Song implements Product
71{
72    private string $name;
73    private float $price;
74
75    public function __construct(string $name, float $price)
76    {
77        $this->name = $name;
78        $this->price = $price;
79    }
80
81    public function getName(): string
82    {
83        return $this->name;
84    }
85
86    public function getPrice(): float
87    {
88        return $this->price;
89    }
90}

2.3.6. نحوه فراخوانی

 1<?php
 2
 3// Usage example
 4$book = new Book("The Hitchhiker's Guide to the Galaxy", 10.99);
 5$gadget = new Gadget("Wireless Headphones", 79.99);
 6$movie = new Movie("Inception", 7.99);
 7$song = new Song("Bohemian Rhapsody", 0.99);
 8
 9$catalog = new ProductCatalog();
10$catalog->addProduct($book);
11$catalog->addProduct($gadget);
12$catalog->addProduct($movie);
13$catalog->addProduct($song);
14
15echo $catalog->getName() . "\n";
16echo "Total price: $" . $catalog->getPrice();

در اینجا یک کاتالوگ از محصولات مورد نظرمون درست می کنیم و در نهایت نام کاتالوگ و قیمت محصولات موجود در کاتالوگ رو در خروجی نمایش میدیم.