3.2. Command

3.2.1. تعریف

دیزاین پترن Command یک الگوی رفتاری محسوب میشه که به ما اجازه میده درخواست (Request) رو به صورت یک آبجکت دربیاریم تا بتونیم با این کار بخش ارسال کننده ی درخواست و بخش اجرا کننده ی درخواست رو از هم جدا کنیم.

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

اینجا سفارش میشه همون Request یا Command و با این کار بخش سفارش دهنده (مشتری) و انجام دهنده سفارش (آشپزخانه رستوان) از هم جدا میشه.

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

در کل این دیزاین پترن میشه گفت که بسیار پر کاربرد هست و در موارد مختلفی استفاده میشه.

اما معمولا در چه مواردی استفاده میشه؟

یکی از کاربردهای اون زمانی هست که قصد دارید درخواست خودتون رو به صورت آبجکت دربیارید و اون رو به عنوان ورودی در اختیار متد مورد نظرتون قرار بدید.

کاربرد دیگه ی اون زمانی هست که قصد دارید در برنامه خودتون queue ها رو پیاده سازی کنید و اون ها رو Schedule کنید یا اون ها رو به صورت Remote اجرا کنید.

و مورد دیگه ی استفاده ی این دیزاین پترن زمانی هست که قصد دارید امکان undo کردن یک دستور رو فراهم کنید، در این حالت کافیه یک متد undo به interface مربوط به Command اضافه کنید و تمام دستورات رو مجبور کنید این متد رو پیاده کنن.

3.2.3. اجزاء

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

ابتدا Command Interface رو داریم که مشخص می کنه هر دستور در برنامه باید چه متدهایی رو پیاده کنه.

بخش بعد خود دستورات هستن که همگی Interface بالا رو پیاده سازی می کنن.

بعد از اون بخشی رو داریم که مدیریت اجرای دستورات رو بر عهده داره و زمانی که لازم باشه متد execute مربوط به دستورات رو فراخوانی می کنه، نام این بخش Invoker هست.

قسمت بعد Receiver هست که در واقع آبجکتی هست که عملیات درخواستی رو اجرا می کنه و به عنوان پارامتر برای دستور ارسال میشه.

در نهایت هم که مثل همیشه Client رو داریم که Request رو برای اجرا initiate می کنه.

UML of Chain of Responsibility Design Pattern

Mormat 13:11, 13 April 2007 (UTC), CC BY-SA 3.0, via Wikimedia Commons

Caution

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

امکان مدیریت روی ترتیب request handling

رعایت اصل Open/Closed از اصول Solid: امکان اضافه کردن دستورات جدید بدون تغییر در کد Client

رعایت اصلی تک مسئولیتی از اصول SOLID طبق توضیحاتی که داده شد

امکان پیاده سازی undo/redo

امکان به تعویق انداختن اجرای دستورات و پیاده سازی صف

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

Warning

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

پیچیده شدن کد به علت تعریف انواع کلاس های جدید بین ارسال کننده دستور و دریافت کننده

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

خب به عنوان مثال میتونیم تصور کنیم که در برنامه خودمون قرار هست یک صف از دستورات ایجاد کنید و به ترتیب اون ها رو اجرا کنیم.

3.2.5. پیاده سازی

ابتدا Interface مربوط به دستورات رو ایجاد می کنیم:

1<?php
2
3// Command interface
4interface Command
5{
6    public function execute();
7
8    public function undo();
9}

و بعد هم دستورات رو به این شکل تعریف می کنیم:

 1<?php
 2
 3// Concrete command class for adding numbers
 4class AddCommand implements Command
 5{
 6    private Receiver $receiver;
 7    private int $value;
 8
 9    public function __construct(Receiver $receiver, int $value)
10    {
11        $this->receiver = $receiver;
12        $this->value = $value;
13    }
14
15    public function execute()
16    {
17        $this->receiver->add($this->value);
18    }
19
20    public function undo()
21    {
22        $this->receiver->subtract($this->value);
23    }
24}
25
26// Concrete command class for subtracting numbers
27class SubtractCommand implements Command
28{
29    private Receiver $receiver;
30    private int $value;
31
32    public function __construct(Receiver $receiver, int $value)
33    {
34        $this->receiver = $receiver;
35        $this->value = $value;
36    }
37
38    public function execute()
39    {
40        $this->receiver->subtract($this->value);
41    }
42
43    public function undo()
44    {
45        $this->receiver->add($this->value);
46    }
47}

کلاس Receiver که عملیات مورد نظر دستورات رو اجرا می کنه به این صورت تعریف میشه:

 1<?php
 2
 3// Receiver class that performs the operations
 4class Receiver
 5{
 6    private int $value = 0;
 7
 8    public function add($value): void
 9    {
10        $this->value += $value;
11    }
12
13    public function subtract($value): void
14    {
15        $this->value -= $value;
16    }
17
18    public function getValue(): int
19    {
20        return $this->value;
21    }
22}

و از این Invoker رو تعریف می کنیم که کارش مدیریت دستورات هست:

 1<?php
 2
 3// Invoker class that manages the commands
 4class Invoker
 5{
 6    private array $commands = [];
 7    private array $undoStack = [];
 8
 9    public function addCommand(Command $command): void
10    {
11        $this->commands[] = $command;
12    }
13
14    public function executeCommands(): void
15    {
16        foreach ($this->commands as $command) {
17            $command->execute();
18            $this->undoStack[] = $command;
19        }
20        $this->commands = [];
21    }
22
23    public function undo(): void
24    {
25        if (!empty($this->undoStack)) {
26            $command = array_pop($this->undoStack);
27            $command->undo();
28        }
29    }
30}

همونطور که میبینید یک لیست از دستورات و undo ها داره و اون ها رو به ترتیب اجرا می کنه.

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

 1<?php
 2
 3// Client code
 4$receiver = new Receiver();
 5$invoker = new Invoker();
 6
 7$invoker->addCommand(new AddCommand($receiver, 5));
 8$invoker->addCommand(new SubtractCommand($receiver, 3));
 9$invoker->executeCommands();
10
11echo "Current value: " . $receiver->getValue() . "\n"; // Output: Current value: 2
12
13$invoker->undo();
14echo "Undone: Current value: " . $receiver->getValue() . "\n"; // Output: Undone: Current value: 5

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