سرفصل‌های آموزشی
آموزش الگوهای طراحی (Design Pattern)
الگوی طراحی Composite

الگوی طراحی Composite

در مهندسی نرم‌افزار، برنامه‌نویسان گاهی با مشکل های مشترکی دست و پنجه نرم می‌کنند. با فراگیرتر شدن این مشکل ها و بحث و تبادل راه حل های برنامه‌نویسان با هم، کم کم راه حلی عمومی برای این مشکل ها ارائه می‌شود. در صورتی که این راه حل ها به مقبولیت بالایی دست یابند در نهایت آن‌ها را نام‌گذاری می‌کنند و به عنوان یک الگوی طراحی (Design Pattern) شناخته می‌شوند. پس الگوهای طراحی در واقع پاسخی عمومی و تکرارپذیر به سؤالاتی است که بین برنامه‌نویسان مشترک است. الگوهای طراحی، طراحی آماده ای نیست که شما به صورت آماده به پروژه ی خود اضافه کنید و بگویید اکنون پروژه از آن الگوی طراحی پیروی می‌کند. بلکه همچون شابلونی است که معیارهای مختلفی که باید رعایت شود تا فرایند توسعه راحت تر شود را مشخص می‌کند.
یکی از دسته بندی هایی که برای الگوهای طراحی استفاده می‌شود، الگوهای طراحی را به سه دسته ی Creational، Structural و Behavioral تقسیم می‌کند. دسته ی اول یعنی Creational به سازوکارهای ایجاد اشیا (Objects) می پردازد تا انعطاف پذیری کد را زیاد کند و قابلیت استفاده مجدد به آن بدهد. الگو های طراحی Structural روش‌هایی را نشان می‌دهند تا اشیا و کلاس‌ها (Classes) را با هم ترکیب کنیم و به ساختار هایی بزرگ‌تر و پیچیده‌تر، دست یابیم درحالی که این ساختار ها همچنان کارآمد و منعطف باقی بمانند. دسته ی آخر یعنی الگوهای طراحی Behavioral به تعامل بین اشیا و توزیع مسئولیت‌ها بین آن‌ها می پردازد و سعی بر بهینه کردن این تعاملات دارد.
در این مقاله یکی از الگوهای طراحی Structural، یعنی الگوی طراحی کامپوزیت (Composite Design Pattern) را بیشتر توضیح می‌دهیم و روش استفاده از آن را با یک مثال نشان می‌دهیم.

الگوی طراحی کامپوزیت

گاهی برنامه‌نویسان با مواردی مواجه می‌شوند که اجزای برنامه ی آن‌ها ساختاری درختی دارند. اگر برنامه‌نویس بخواهد از یک جزء، بدون اطلاع از زیرشاخه‌های آن، فرایندی را درخواست کند که تمام زیرشاخه ها را درگیر می‌کند، به مشکل بر خواهد خورد. در اینجا با استفاده از الگوی طراحی کامپوزیت این مشکل رفع می‌شود.

طرح مسئله

در بعضی از فرایندها نیاز است تا یک مجموعه ی سلسله مراتبی را مدیریت کنیم. درواقع ساختار اجزا (Components)، ساختاری درختی است که دو نوع جزء دارد. نوع اول اجزایی هستند که به آن‌ها جزء اساسی (Primitive Component) می‌گوییم. این نوع ساده‌تر ین جزء درخت مجموعه است که به آن‌ها برگ (Leaf) هم می‌گویند. نوع دوم، اجزای مرکب (Composite) اند که از جمع شدن اجزای نوع اول به وجود می آیند. بگذارید این تعریف ها را در قالب مثال شرح دهیم. یک شرکت طراحی و توسعه ی وب‌سایت را در نظر بگیرید. مدیر این شرکت می‌تواند از زیر مجموعه‌های خود بخواهد که گزارش کارهایشان را ارسال کنند. ساختار شرکت را به صورت زیر در نظر می‌گیریم.

شکل 1 ساختار شرکت

توسعه‌دهنده ها یا همان برنامه‌نویسان اجزای اساسی اند. شرکت به گروه هایی تقسیم شده است. این گروه ها همان اجزای نوع دوم یا اجزای مرکب هستند. حال هرگروه شامل اجزای اساسی یا همان برنامه‌نویس ها است. فرض کنید اکنون مدیر نیاز دارد تا تمام افراد شرکت گزارش های خود را ارسال کنند. اگر مدیر بخواهد مستقیماً خود دستور گزارش گیری را بدهد باید به هر زیر مجموعه ی خود سر بزند و اگر آن زیر مجموعه، خود افراد زیرشاخه داشت تمام آن افراد را لیست کند و در نهایت به به تمامی افراد دستور فرستادن گزارش را بدهد. این کار بسیار پرهزینه بوده و بهینه نیست. اما راه حل چیست؟ الگوی طراحی کامپوزیت می‌تواند این فرایند را آسان کند.

الگوی طراحی کامپوزیت، راه حل مشکل

برای حل این مشکل، شما می‌باید با گروه ها و برنامه‌نویس های شرکت، از طریق یک رابط مشترک که فرایند گزارش گیری را تعریف می‌کند کار کنید. روند کار به این صورت است که برنامه‌نویس مستقیماً گزارش را ارسال می‌کند. در مقابل هر گروه از تک‌تک زیرمجموعه‌های خود درخواست گزارش می‌کند و مجموعه ی گزارش ها را ارسال می‌کند. اگر یکی از زیرمجموعه ها خود یک گروه بود همین کار را تکرار می‌کند و از تمام زیرمجموعه‌های خود گزارش گیری می‌کند و مجموعه را ارسال می‌کند. این کار تا جایی ادامه پیدا می‌کند تا تمام گزارش ها ارسال شوند. این روند همان الگوی طراحی کامپوزیت است.
بزرگ‌ترین مزیت این الگوی طراحی این است که دیگر نیازی نیست شما به نوع جزئی که با آن کار می‌کنید توجه کنید. نیازی نیست بدانید که جزء مقابل شما یک برنامه‌نویس است یا گروهی از برنامه‌نویسان. شما می‌توانید با همه ی آن‌ها از طریق رابط مشترک به صورت یکسان رفتار کنید. وقتی شما یک فرایند را درخواست می‌کنید خود اجزا دستور را به زیرشاخه‌های درخت می رسانند.
ساختار این الگوی طراحی به صورت زیر است:

شکل 2 ساختار الگوی طراحی کامپوزیت

حال که با کاربرد کلی این الگوی طراحی آشنا شدیم بگذارید تا یکبار دیگر مرور کنیم؛ شاید کمی تخصصی تر!
الگوی طراحی کامپوزیت جایی استفاده می‌شود که بخواهیم با مجموعه ای از اشیا همانند یک شیئ برخورد کنیم. در واقع هدف کامپوزیت ترکیب کردن شیئ ها به منظور نمایش دادن سلسله مراتب های جزء-کل (Part-Whole Hierarchy) است. شما می‌توانید یک ساختار درختی داشته باشید و از هرگره بخواهید یک وظیفه را انجام دهد.
برنامه‌نویسان هنگامی که با داده‌هایی با ساختار درختی سروکار دارند اغلب باید بین شاخه ها و برگ ها تمایز قائل شوند. این روش باعث پیچیده‌تر شدن کد و بالا رفتن امکان خطا می‌شود. الگوی طراحی کامپوزیت به کاربر اجازه می‌دهد با هر یک از اشیا، فارغ از این که یک شیئ است یا مرکب از اشیا، به صورت یکسان رفتار کند. در برنامه‌نویسی شیئ‌گرا، شیئ مرکب از ترکیب یک یا بیشتر از یک شیئ یکسان ساخته می‌شود. نکته‌ای که الگوی طراحی کامپوزیت از آن برای رفع این مشکل بهره می برد، استفاده از یک رابط است که اجازه بدهد با یک شیئ همان‌طور رفتار کنیم که با گروهی از آن شیئ رفتار می‌کنیم.


اجزای تشکیل دهنده ی الگوی طراحی کامپوزیت


الگوی طراحی کامپوزیت چهار جزء دارد: Component که در این متن جزء استفاده شده است، Leaf یا برگ، Composite که در این مقاله به عنوان شیئ مرکب استفاده می‌شود و Client یا کاربر.
1. جزء: جزء رابطی (Interface) است که رفتار مشترک بین تمام زیرشاخه ها را مشخص می‌کند و همه ی اجزا باید از آن پیروی کنند.
2. برگ: پایه ای ترین شیئ است. اکثر اوقات همین برگ است که کار واقعی را انجام می‌دهد.
3. شیئ مرکب: مجموعه ای از فرزندان را دارد که هر یک از این فرزندان می‌توانند خود یک شیئ مرکب دیگر باشند یا یک برگ باشند. عموماً شیئ مرکب کار را به شاخه های پایین تر محول می‌کند. شیئ مرکب همچنین عملیات مربوط به مدیریت فرزندان را انجام می‌دهد.
4. کاربر: کاربر کسی است که با استفاده از رابط تعریف شده در جزء (Component interface) با اشیا کار می‌کند.

یک نکته درمورد Component Interface


باید توجه داشت که در رابطه با عملیات مدیریت فرزند، که شامل متدهای add، remove و getChild می‌شود، می‌توان دو روش را در پیش گرفت.
یک راه این است که این عملیات را در Component interface یا همان جزء مشخص کنیم. در این صورت کاربر می‌تواند با تمام شیئ ها، فارغ از نوع آن‌ها، یکسان رفتار کند. اما این روش دو مشکل دارد. یک این که ممکن است کاربر عملیاتی بر روی برگ انجام دهد (عملیات مدیریت فرزند) که منطقی نیست. دوم این مسئله است که با این کار یکی از قوانین SOLID (Interface Segregation Principle) را نقض می‌کنیم؛ بدین صورت که در کلاس برگ متدهایی وجود خواهد داشت که خالی اند.
در روش دوم متدهای مربوط به مدیریت فرزندان فقط در کلاس شیئ مرکب تعریف شوند. با این کار امکان برخورد یکسان را از دست می‌دهیم اما از انجام عملیات غیرمنطقی جلوگیری می‌کنیم.

نکات مثبت و منفی

از نکات مثبت استفاده از این الگوی طراحی، می‌توان به راحتی کار با ساختارهای درختی با استفاده از ویژگی‌های چند ریختی و بازگشتی و همچنین رعایت قاعده ی Open/Closed اشاره کرد. در واقع با رعایت این قاعده، می‌توان انواع جدیدی از شیئ ها را بدون این که کد موجود را از کار بیندازیم، به ساختار درختی معرفی کنیم.
از طرف دیگر تهیه ی یک رابط عمومی برای کلاس‌هایی که کارکرد آن‌ها خیلی با هم تفاوت دارد ممکن است سخت باشد. در بعضی موارد ممکن است نیاز پیدا کنید که رابط را بیش از حد عمومی تعریف کنید که درک کد را سخت تر می‌کند.

پیاده سازی الگوی طراحی کامپوزیت در php

بگذارید با مرور یک مثال از پیاده سازی این الگوی طراحی در php به درک عمیق تری از آن برسیم. فرض کنید در یک سایت دوره‌های آموزشی، نیاز به صفحه‌ای دارید تا از طریق آن دوره‌های جدید را اضافه کنید. هر دوره دارای نام، شرح دوره، علامت تجاری دوره و توضیح علامت تجاری است. برای افزودن دوره ی جدید نیاز به یک فرم دارید. هر فرم از اجزای کوچک‌تر مانند فیلد های ورودی متنی، فیلد بارگزاری عکس، گروهی از فیلدهای مرتبط و ... تشکیل شده است. تمام این قسمت ها به ارائه دادن خود در قالب HTML نیاز دارند. به طور مثال یک فیلد متنی باید بتواند کد HTML خود را تولید کند. همین‌طور یک فرم باید بتواند خود را به صورت HTML ارائه دهد. پس در اینجا می‌توان ساختار درختی موجود را مشاهده کرد و برای پیاده سازی این موضوع که تمام گره های این درخت باید بتوانند خود را به صورت HTML ارائه دهند، از الگوی طراحی کامپوزیت استفاده می‌کنیم.
ابتدا باید یک رابط مشترک برای این کار تعریف کنیم و از طریق آن با اجزا کار کنیم. بگذارید در ابتدا نمودار ساختار برنامه را ببینیم.

شکل 3 نمودار ساختار مثال php
همین‌طور که در شکل ۳ مشخص است تمام اجزای فرم باید متد render ، که وظیفه ی تولید کد HTML را دارد، داشته باشند. در این مثال کلاس Input، برگ محسوب می‌شود. برای اجزای مرکب به منظور جلوگیری از تکرار، یک کلاس پایه ای تعریف می‌کنیم به نام FieldComposite که عملیات مدیریت فرزندان را پیاده سازی می‌کند و همچنین در متد render، خروجی کد HTML تمام فرزندان خود را به هم می چسباند و به عنوان خروجی می‌دهد. در ساختار اصلی، دو نوع شیئ مرکب داریم؛ یکی Fieldset که چند Input مرتبط را شامل می‌شود و دیگری Form که کلیه ی اجزای دیگر را شامل می‌شود.
برای جلوگیری از پیچیدگی‌های استفاده از namespace و تمرکز بیشتر بر روی الگو، کلیه کلاس‌ها در یک فایل با نام index.php نوشته شده‌اند.
کد نوشته شده برای این مثال در زیر آورده شده است.

<?php

/**
 * The base Component class declares an interface for all concrete components,
 * both simple and complex.
 *
 * In our example, we'll be focusing on the rendering behavior of DOM elements.
 */
interface FormElement
{
    public function getName(): string;

    public function setData($data): void;

    public function getData(): array;

    /**
     * Each concrete DOM element must provide its rendering implementation, but
     * we can safely assume that all of them are returning strings.
     */
    public function render(): string;
}

/**
 * This is a Leaf component. Like all the Leaves, it can't have any children.
 */
class Input implements FormElement
{
    private $type;
    private $title;
    private $name;
    private $data;

    public function __construct(string $name, string $title, string $type)
    {
        $this->name = $name;
        $this->title = $title;
        $this->type = $type;
    }

    public function getName(): string
    {
        return $this->name;
    }

    public function setData($data): void
    {
        $this->data = $data;
    }

    public function getData(): array
    {
        return $this->data;
    }


    /**
     * Since Leaf components don't have any children that may handle the bulk of
     * the work for them, usually it is the Leaves who do the most of the heavy-
     * lifting within the Composite pattern.
     */
    public function render(): string
    {
        return "<label for=\"{$this->name}\">{$this->title}</label>\n" .
            "<input name=\"{$this->name}\" type=\"{$this->type}\" value=\"{$this->data}\">\n";
    }
}

/**
 * The base Composite class implements the infrastructure for managing child
 * objects, reused by all Concrete Composites.
 */
abstract class FieldComposite implements FormElement
{
    /**
     * holds the children of a composite
     */
    protected $fields = [];

    protected $name;
    protected $title;
    protected $data;

    public function __construct(string $name, string $title)
    {
        $this->name = $name;
        $this->title = $title;
    }

    public function getName(): string
    {
        return $this->name;
    }

    /**
     * The methods for adding/removing sub-objects.
     */
    public function add(FormElement $field): void
    {
        $this->fields[$field->getName()] = $field;
    }

    public function remove(FormElement $component): void
    {
        $this->fields = array_filter($this->fields, function ($child) use ($component) {
            return $child != $component;
        });
    }

    /**
     * Whereas a Leaf's method just does the job, the Composite's method almost
     * always has to take its sub-objects into account.
     *
     * In this case, the composite can accept structured data.
     *
     * @param array $data
     */
    public function setData($data): void
    {
        foreach ($this->fields as $name => $field) {
            if (isset($data[$name])) {
                $field->setData($data[$name]);
            }
        }
    }

    /**
     * The same logic applies to the getter. It returns the structured data of
     * the composite itself (if any) and all the children data.
     */
    public function getData(): array
    {
        $data = [];

        foreach ($this->fields as $name => $field) {
            $data[$name] = $field->getData();
        }

        return $data;
    }

    /**
     * The base implementation of the Composite's rendering simply combines
     * results of all children. Concrete Composites will be able to reuse this
     * implementation in their real rendering implementations.
     */
    public function render(): string
    {
        $output = "";

        foreach ($this->fields as $name => $field) {
            $output .= $field->render();
        }

        return $output;
    }
}

/**
 * The fieldset element is a Concrete Composite.
 */
class Fieldset extends FieldComposite
{
    public function render(): string
    {
        // Note how the combined rendering result of children is incorporated
        // into the fieldset tag.
        $output = parent::render();

        return "<fieldset><legend>{$this->title}</legend>\n$output</fieldset>\n";
    }
}

/**
 * And so is the form element.
 */
class Form extends FieldComposite
{
    private $url;
    private $method;

    public function __construct(string $name, string $title, string $url, string $method = 'post')
    {
        parent::__construct($name, $title);
        $this->url = $url;
        $this->method = $method;
    }

    public function render(): string
    {
        $output = parent::render();
        return "<form action=\"{$this->url}\" method=\"{$this->method}\">\n
        <h3>{$this->title}</h3>\n{$output}<input type='submit'></form>\n";
    }
}

/**
 * The client code gets a convenient interface for building complex tree
 * structures.
 */
function getCourseForm(): FormElement
{
    $form = new Form('course', "Add course", "/course/add");
    $form->add(new Input('name', "Name", 'text'));
    $form->add(new Input('description', "Description", 'text'));

    $picture = new Fieldset('photo', "Course Logo");
    $picture->add(new Input('caption', "Caption", 'text'));
    $picture->add(new Input('image', "Image", 'file'));
    $form->add($picture);

    return $form;
}

/**
 * The form structure can be filled with data from various sources. The Client
 * doesn't have to traverse through all form fields to assign data to various
 * fields since the form itself can handle that.
 */
function loadCourseData(FormElement $form)
{
    $data = [
        'name' => 'PHP Course',
        'description' => 'PHP for Dummies',
        'photo' => [
            'caption' => 'Course Logo',
        ],
    ];

    $form->setData($data);
}

/**
 * The client code can work with form elements using the interface.
 * This way, it doesn't matter whether the client works with a simple component
 * or a complex composite tree.
 */
function renderCourse(FormElement $form)
{
    // ..

    echo $form->render();

    // ..
}

$form = getCourseForm();
loadCourseData($form);
renderCourse($form);

پیاده سازی الگوی طراحی کامپوزیت در Laravel

در این قسمت یک مثال عملی از پیاده سازی این الگوی طراحی در فریم ورک Laravel برای زبان php شرح داده شده است.
فرض کنید ما در فروشگاه آنلاین خود سه محصول داریم و کاربر می‌تواند روش بسته بندی را مشخص کند. کاربر، سفارشی به صورت زیر ثبت می‌کند. حال ما می‌خواهیم قیمت تمام شده برای کاربر را نمایش دهیم.

شکل 4 یک نمونه از سفارش کاربر

با استفاده از الگوی طراحی کامپوزیت به راحتی می‌توانیم قیمت تمام شده را به دست آوریم. برای راحتی و تمرکز بر الگوی طراحی سعی شده است تا حد ممکن کلاس‌ها ساده و بدون موارد اضافی باشند.
از تعریف یک رابط عمومی برای تمام اجزا شروع می‌کنیم. درون دایرکتوری app یک دایرکتوری به عنوان Entities ایجاد می‌کنیم. حال در این دایرکتوری یک فایل به عنوان PricingInterface ایجاد می‌کنیم. محتوای فایل به صورت زیر است و اجزای آن را پیاده سازی می‌کنند. رابط PricingInterface این امکان را فراهم می‌کند که بتوانیم از تمام کلاس‌های تابع آن، قیمتشان را بپرسیم. این رابط همان جزء یا Component در نمودار الگوی طراحی است و تمام اشیای مرکب و برگ ها می‌باید آن را پیاده سازی کنند.

<?php


namespace App\Entities;


interface PricingInterface
{
    public function price(): float;
}

در مرحله ی بعد باید یک کلاس از نوع محصولاتمان تعریف کنیم. هر محصول یک نام دارد که نوع آن را مشخص می‌کند و یک قیمت. کلاس محصول، رابط PricingInterface را پیاده می‌کند. این کلاس همان جزء پایه ای درخت ما است.

<?php

namespace App\Entities;

class Product implements PricingInterface
{
    private string $name;
    private float $price;

    public function __construct(string $name, float $price)
    {
        $this->name = $name;
        $this->price = $price;
    }

    /**
     * @return float
     */
    public function price(): float
    {
        return $this->price;
    }

    /**
     * @return string
     */
    public function name(): string
    {
        return $this->name;
    }
}

حال باید جزء مرکب را که در این مثال سفارش ها و بسته ها هستند، تعریف کنیم. ابتدا بسته ها را تعریف می‌کنیم. هر بسته یک هزینه به عنوان هزینه ی بسته بندی دارد. کلیه محتویات جعبه ها نیز درون یک آرایه ریخته می‌شوند و با استفاده از توابع تعریف شده می‌توان آن‌ها را مدیریت کرد. همانگونه که مشخص است هم کلاس Box و هم Order رابط PricingInterface را پیاده می‌کنند.

<?php
namespace App\Entities;

class Box implements PricingInterface
{
    private float $boxingCost;
    private array $items;

    public function __construct(float $boxingCost, array $items = [])
    {
        $this->boxingCost = $boxingCost;
        $this->items = $items;
    }

    public function price(): float
    {
        $totalItemsPrice = 0;
        foreach ($this->items as $item) {
            $totalItemsPrice += $item->price();
        }

        /*returns the total price of contents plus boxing cost*/
        return $totalItemsPrice + $this->boxingCost;
    }

    /**
     *
     * @param PricingInterface $item
     */
    public function addItem(PricingInterface $item)
    {
        $this->items[] = $item;
    }

    /**
     * pops out the last item in box, null if box is empty
     *
     * @return PricingInterface|null
     */
    public function popItem(): ?PricingInterface
    {
        if (empty($this->items)) {
            return null;
        }

        return array_pop($this->items);
    }

}

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

<?php

namespace App\Entities;

interface OrderInterface
{
    /**
     * @return array
     */
    public function items(): array;

    /**
     * @return int
     */
    public function id(): int;
}


کلاس سفارش نیز به صورت زیر تعریف می‌شود.

<?php
namespace App\Entities;
class Order implements OrderInterface, PricingInterface
{
    private int $userId;
    private int $id;
    private array $items;
    private float $price = 0;
    /**
     * @param int $userId
     * @param int $id
     * @param array $items
     */
    public function __construct(int $userId, int $id, array $items)
    {
        $this->userId = $userId;
        $this->id = $id;
        $this->items = $items;
    }
    public function id(): int
    {
        return $this->id;
    }
    /**
     * @return array
     */
    public function items(): array
    {
        return $this->items;
    }
    public function price(): float
    {
        foreach ($this->items as $item) {
            $this->price += $item->price();
        }
        return $this->price;
    }
}

قبل از این که بخواهیم روند محاسبه و نشان دادن قیمت نهایی را در در کنترلر پیاده سازی کنیم باید یک دسته سفارش ایجاد کنیم تا به عنوان دیتابیس به ما کمک کنند. برای این کار یک کلاس با نام Orders در دایرکتوری app می‌سازیم. این کلاس به عنوان یک مجموعه از سفارش ها عمل می‌کند.

<?php


namespace App;


use App\Entities\OrderInterface;

class Orders
{
    private $items = [];

    /**
     * @param OrderInterface $order
     */
    public function add(OrderInterface $order)
    {
        $this->items[$order->id()] = $order;
    }

    /**
     * @param int $orderId
     *
     * @return OrderInterface|null
     */
    public function find(int $orderId): ?OrderInterface
    {
        if (!array_key_exists($orderId, $this->items)) {
            return null;
        }

        return $this->items[$orderId];
    }

    /**
     * @return array
     */
    public function items(): array
    {
        return $this->items;
    }
}

این کلاس به ما اجازه می‌دهد که سفارش خودمان را در بین سفارش های ثبت شده بیابیم و همچنین می‌توانیم یک سفارش جدید ثبت کنیم. سؤال بعدی این است که بدون نیاز به دیتابیس چگونه لیست سفارش ها را به برنامه معرفی کنیم؟ این کار با استفاده از container های لاراول به راحتی قابل انجام است. در قسمت providers فایل AppServiceProvider.php را باز کنید. ما باید در متد register سفارش های خود را ثبت کنیم و به کلاس Orders متصل یا به اصطلاح bind کنیم تا هر کجا از برنامه بتوانیم به سفارش ها دست پیدا کنیم. محتویات این فایل برای مثال ما به صورت زیر می‌شود.

<?php

namespace App\Providers;

use App\Entities\Box;
use App\Entities\Order;
use App\Entities\Product;
use App\Orders;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        // Set up our dummy Products and Order database
        $apple = new Product("Apple", 8.45);
        $pen = new Product("Pen", 15);
        $pineapple = new Product("Pineapple", 22.2);

        $smallBox1 = new Box(2, [$apple, $pineapple]);
        $mediumBox1 = new Box(4, [$smallBox1, $pen, $pen]);
        $mediumBox2 = new Box(4, [$apple, $apple]);
        $bigBox1 = new Box(10, [$mediumBox1, $pen, $mediumBox2]);

        $order1 = new Order(1, 1, [$bigBox1]);

        $orders = new Orders();

        $orders->add($order1);

        $this->app->bind(Orders::class, function () use ($orders) {
            return $orders;
        });
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }
}

حال می‌باید برای انجام عملیات یک کنترلر تعریف کنیم به نام OrderController. این کار را با دستور زیر انجام می‌دهیم:
php artisan make:controller OrderController
همچنین یک مسیر به این کنترلر و متد مورد نظرمان در web.php تعریف می‌کنیم.

Route::get('/orderPrice/{orderId}', 'OrderController@showOrderPrice');

در آخرین مرحله باید عملیات مربوط به محاسبه ی قیمت و نشان دادن آن را در کنترلر، درون متد مربوط پیاده سازی کنیم.

<?php

namespace App\Http\Controllers;

use App\Orders;
use Illuminate\Http\Request;

class OrderController extends Controller
{
    private Orders $orders;

    public function __construct(Orders $orders)
    {
        //getting orders list
        $this->orders = $orders;
    }

    public function showOrderPrice(int $orderId)
    {
        $order = $this->orders->find($orderId);

        return $order->price();
    }
}

دقت کنید که ما در کانستراکتور (متد __construct) کنترلر یک متغیر با نام orders و از جنس کلاس Orders به عنوان ورودی مشخص کرده‌ایم. لاراول این مسئله را در پشت پرده برای ما حل می‌کند. از آنجا که ما در قسمت قبل مشخص کردیم که هرکجا از برنامه نیاز به شیئی از نوع Orders شد، نمونه‌ای که ما درست کرده‌ایم را به عنوان ورودی معرفی کند، لاراول این کار را برای ما انجام می‌دهد و ما تمام سفارش ها را در property ی orders$ داریم و در کنترلر می‌توانیم با this->orders$ به آن دسترسی داشته باشیم. دقت کنید که ما فقط یک سفارش با id=1 تعریف کرده‌ایم.

در آخر اگر با دستور php artisan serve یک سرور بسازیم و آدرس زیر را در مرورگر خود وارد کنیم قیمت سفارش اول را که ثبت شده است می‌بینیم.

http://127.0.0.1:8000/orderPrice/1

نتیجه:

شکل 5 خروجی مثال در Laravel

جمع‌بندی

در این مقاله ابتدا با الگوی طراحی کامپوزیت آشنا شدیم و سپس یک مثال عملی از آن را پیاده سازی کردیم. همان‌طور که دیدید این الگو زمانی کاربرد دارد که کاربر نیاز دارد تفاوت بین شیئ هایی که از ترکیب چند شیئ به وجود آمده‌اند را با یک شیئ در نظر نگیرد و بتواند با همه ی آن‌ها به صورت یکسان برخورد کند. در مواقعی که برنامه‌نویس در می‌یابد که از چندین شیئ به صورت یکسان استفاده می‌کند و کد مورد نیاز برای انجام کار نیز برای هرکدام تقریبا شبیه به هم است این الگوی طراحی انتخاب مناسبی است.
اما همیشه این نکته را در نظر داشته باشید که اگر نمی خواهید یک رابطه ی درختی را نمایش دهید، استفاده از این الگو پیشنهاد نمی‌شود. همچنین این الگوی طراحی می‌تواند طراحی کلی را بیش از حد عمومی‌کند. در این حالت محدود کردن نوع اشیا سخت می‌شود. برای مثال اگر یک جزء مرکب فقط باید از چند شيء مشخص ساخته شود، می‌باید در طول زمان اجرا آن را چک کرد و نمی‌توان از type system استفاده کرد.

online-support-icon