مهارت های برنامه نویسی با کد SOLID خود را با این تکنیک ساده و در عین حال قدرتمند کلاس های مستقل بهبود بخشید.

امروز می خواهم یک تکنیک برنامه نویسی مفید را با شما به اشتراک بگذارم که هر روز در کار حرفه ای خود از آن استفاده می کنم.

آیا تا به حال در مورد کلاس های قابل استناد  شنیده اید که کلاس های فراخوانی نیز نامیده می شوند؟ مطمئناً برخی از شما با آنها روبرو شده اید، به عنوان مثال در به اصطلاح "کنترل کننده های تکی" در چارچوب های مورد علاقه خود مانند Laravel یا Symfony.

 

کلاس های فراخوانی مدتی است که در PHP وجود دارند. من شخصاً آنها را سالها پیش دیدم، اما اخیراً از زیبایی آنها قدردانی کردم

 

کلاس قابل فراخوانی چیست؟

فقط کلاسی است که متد جادویی __invoke تعریف شده است:

 

<?php
                  class Adder
                   {

                         public function __invoke(int $a, int $b): int

                         {

                                return $a + $b;

                          }

                   }

من فرض می‌کنم حداقل از PHP نسخه 7 استفاده می‌کنید. روش ترجیحی برای اجرای چنین کلاسی ساده است. اگر یک ویژگی کلاس قابل احضار باشد، باید با پرانتز احاطه شود، به عنوان مثال:

 

<?php

            class Calculator

            {

                public function __construct(

                      private Adder $adder

                 ) { }

                public function add(): int

                {

                      return ($this->adder)(2, 3);

                }

             }

              $calculator = new Calculator();

              echo $calculator->add(); // prints 5

اگر یک متغیر قابل فراخوانی است، به سادگی:

$adder = new Adder( );

echo $adder(3, 4); // prints 7

کلاس های فراخوانی چقدر در SOLID مفید هستند؟

ستاره قسمت امروز حرف "S" از مخفف "SOLID" یا "Single Responsibility Principle" است. من فرض می‌کنم که نمی‌توانید منتظر بمانید تا ببینید کلاس‌های فراخوانی چگونه می‌توانند به شما در ایجاد کد بهتر و زیباتر کمک کنند.

 

قوانین ساده هستند. کلاس قابل فراخوانی:

 

  • قرار است یک رفتار واحد داشته باشد (مثلاً "یک محصول را به یک سبد اضافه کنید"، "یک محصول را از یک سبد خارج کنید" و …)
  • باید فقط متد "__invoke" را داشته باشد و نمی تواند هیچ روش عمومی دیگری داشته باشد
  • می تواند برای نیازهای داخلی هر تعداد که می خواهد روش خصوصی داشته باشد
  • نام آن باید رفتار آن را نشان دهد. فقدان روش های عمومی با نام خاص این قانون را اجرا می کند (در ادامه در مورد آن بیشتر توضیح خواهیم داد)
  • کاندیدای خوب برای کلاس های فراخوانی

 

هر کلاسی که قرار است کار خاصی انجام دهد:

  • ارائه دهندگانی که یک مقدار را برمی گردانند (TaxAmountProvider، ConfigurationProvider، SomeDataProvider، و …)
  • اعتبار سنجی (EmailValidator، VatinValidator، AgeValidator، و …)
  • کنترلرها (LoginAction، HomepageAction، RegistrationFormAction، و …)
  • مشخصات (CanAddProductSpecification، IsSuperAdminSpecification، و …)
  • خدمات (CalculateTaxService، AddToBasketService، و …)

 

 

مثال زندگی واقعی

من می خواهم یک مثال واقعی را نشان دهم. بیایید یک سرویس مدیریت سفارش معمولی را در نظر بگیریم:

 

<?php

                    class OrderService

                    {

                          public function addProduct(Order $order, Product $product): void { }

                          public function removeProduct(Order $order, Product $product): void { }

                          public function addTax(Order $order, Tax $tax): void { }

                          public function addShippingCost(Order $order, ShippingCost $cost): { }

                          public function empty(Order $order): void { }

                     }

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

<?php

 

                   class PromotionalOrderService extends OrderService

                   {

                        public function addGratisProduct(Order $order, Product $product): void { }

                        public function removeGratisProduct(Order $order, Product $product): void { }

                        public function applyDiscount(Order $order, DiscountCalculator $calculator): 

                    void {}

                        public function sendPromotionalEmail(Order $order, User $user): void {}

                    }

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

کلاس های فراخوانی برای نجات

اگر از تکنیک فوق العاده کلاس های فراخوانی استفاده کنیم چه؟ ما به چیزی شبیه این دست خواهیم یافت:

 

<?php

                class AddToOrder {

                     public function __invoke(Order $order): void { }

                 }

                class RemoveFromOrder {

                     public function __invoke(Order $order, Product $product): void { }

                 }

                class EmptyOrder {

                     public function __invoke(Order $order, Product $product): void { }

                  }

                class AddTaxesToCart {

                     public function __invoke(Order $order, Tax $tax): void { }

                  }

                 class AddShippingCostToCart {

                      public function __invoke(Order $order, ShippingCost $cost): void { }

                  }

زیبا نیست؟

 

  • جدایی نگرانی واقعی،
  • کلاس ها نمی توانند بیش از یک مسئولیت داشته باشند ("S" از "SOLID") به دلیل اینکه فقط یک روش عمومی مجاز است،
  • عملکرد کلاس بدون مرور کد واضح است،
  • پوشش دادن کلاس ها با آزمون های واحد بسیار بسیار آسان تر است،
  • تست های واحد می توانند دقیق تر باشند،
  • اقدامات سفارش به طور بی پایان قابل توسعه هستند،
  • اقدامات سفارش را می توان به طور جداگانه تغییر داد که منجر به رگرسیون کمتر می شود.

 

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

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