SOLID چیست؟ آموزش کامل اصول SOLID با مثال‌های واقعی در PHP و Laravel

SOLID چیست؟ آموزش کامل اصول SOLID با مثال‌های واقعی در PHP و Laravel

  • سطح : متوسط
  • مطالعه : 30 دقیقه
  • 6

SOLID چیست؟

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

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

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

در این مقاله چه چیزهایی یاد می‌گیرید؟

  • SOLID چیست.
  • چرا SOLID ایجاد شد.
  • هر یک از پنج اصل SOLID چه مشکلی را حل می‌کند.
  • کاربرد SOLID در پروژه‌های واقعی.
  • پیاده‌سازی اصول SOLID در PHP و Laravel.
  • ارتباط SOLID با Design Pattern.
  • ارتباط SOLID با Clean Architecture.
  • اشتباهات رایج هنگام استفاده از SOLID.

پیش‌نیازهای مطالعه

برای مطالعه این مقاله بهتر است با مفاهیم زیر آشنا باشید.

  • Object-Oriented Programming
  • Class و Object
  • Inheritance
  • Interface
  • Polymorphism

اگر این مفاهیم را می‌شناسید، درک SOLID برای شما بسیار ساده‌تر خواهد شد.

SOLID چیست؟

SOLID مجموعه‌ای از پنج اصل طراحی در برنامه‌نویسی شی‌گرا است که به توسعه‌دهندگان کمک می‌کند نرم‌افزارهای قابل توسعه و قابل نگهداری طراحی کنند.

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

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

SOLID چه چیزی نیست؟

یکی از اشتباهات رایج این است که SOLID را با Design Pattern یا Framework اشتباه می‌گیرند.در واقع SOLID هیچ‌کدام از این دو نیست.

مفهوم توضیح
SOLID مجموعه‌ای از اصول طراحی نرم‌افزار
Design Pattern راه‌حل برای مشکلات رایج طراحی
Framework ابزاری برای توسعه نرم‌افزار

در حقیقت بسیاری از Design Patternها برای پیاده‌سازی عملی همین اصول طراحی شده‌اند.

تاریخچه SOLID

این اصول توسط Robert C. Martin که بیشتر با نام Uncle Bob شناخته می‌شود معرفی شدند.هدف او ارائه مجموعه‌ای از قوانین ساده بود تا پروژه‌ها با گذشت زمان کیفیت خود را از دست ندهند.

امروزه SOLID یکی از مهم‌ترین مباحث مهندسی نرم‌افزار محسوب می‌شود و در بیشتر معماری‌های مدرن نقش اساسی دارد.

چرا SOLID اهمیت دارد؟

فرض کنید توسعه یک فروشگاه اینترنتی را آغاز کرده‌اید.در نسخه اول فقط امکان ثبت سفارش وجود دارد و همه چیز ساده به نظر می‌رسد.

چند ماه بعد قابلیت‌هایی مانند پرداخت آنلاین، کیف پول، کد تخفیف، پیامک، ایمیل، فاکتور و ارسال کالا به پروژه اضافه می‌شوند.

اگر از ابتدا ساختار مناسبی طراحی نشده باشد، تقریباً تمام کلاس‌های پروژه به یکدیگر وابسته خواهند شد.

در چنین شرایطی تغییر یک متد ساده ممکن است باعث ایجاد باگ در بخش‌های مختلف سیستم شود.

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

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

مزیت نتیجه
خوانایی بیشتر درک پروژه برای اعضای تیم آسان‌تر می‌شود.
توسعه آسان‌تر افزودن قابلیت‌های جدید ساده‌تر خواهد شد.
تست‌پذیری نوشتن Unit Test راحت‌تر می‌شود.
نگهداری ساده هزینه تغییرات آینده کاهش پیدا می‌کند.
کاهش باگ وابستگی کمتر، خطاهای جانبی را کاهش می‌دهد.

سناریوی این مقاله

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

در هر اصل فقط همین پروژه را توسعه می‌دهیم تا ارتباط بین مثال‌ها حفظ شود.به این ترتیب مشاهده خواهید کرد که هر اصل چگونه بخشی از مشکلات این پروژه را حل می‌کند.

SOLID مخفف چیست؟

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

حرف نام اصل هدف
S Single Responsibility Principle هر کلاس فقط یک مسئولیت داشته باشد.
O Open Closed Principle توسعه بدون تغییر کلاس‌های موجود انجام شود.
L Liskov Substitution Principle کلاس فرزند بتواند جایگزین کلاس والد شود.
I Interface Segregation Principle Interfaceهای کوچک و تخصصی طراحی شوند.
D Dependency Inversion Principle وابستگی به Interface انجام شود.

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

ارتباط بین پنج اصل SOLID

یکی از اشتباهات رایج این است که هر اصل SOLID را به صورت مستقل یاد بگیریم.در عمل این پنج اصل مانند قطعات یک پازل هستند و در کنار یکدیگر معنا پیدا می‌کنند.

برای مثال اگر فقط از اصل SRP استفاده کنید، اما همچنان کلاس‌ها وابستگی مستقیم داشته باشند، پروژه هنوز انعطاف‌پذیر نخواهد بود.

همچنین رعایت OCP بدون استفاده از Interfaceها معمولاً امکان‌پذیر نیست و اصل DIP نیز بدون Abstraction ارزش چندانی نخواهد داشت.

توسعه‌دهندگان حرفه‌ای این اصول را به عنوان یک مجموعه واحد در نظر می‌گیرند، نه پنج قانون جداگانه.

مشکل اصلی قبل از یادگیری SOLID

قبل از اینکه وارد اولین اصل شویم، بهتر است ببینیم نبود SOLID چه مشکلاتی ایجاد می‌کند.فرض کنید تمام منطق فروشگاه اینترنتی داخل یک کلاس قرار گرفته است.

class OrderService
{
    public function createOrder()
    {
        // Save Order
    }

    public function processPayment()
    {
        // Payment
    }

    public function sendSms()
    {
        // SMS
    }

    public function sendEmail()
    {
        // Email
    }

    public function createInvoice()
    {
        // Invoice
    }

    public function decreaseInventory()
    {
        // Inventory
    }
}

در نگاه اول شاید این کلاس مشکلی نداشته باشد، اما با رشد پروژه شرایط کاملاً تغییر خواهد کرد.

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

اگر تیم انبار منطق موجودی را تغییر دهد، همین کلاس دوباره تغییر می‌کند.

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

در نتیجه یک کلاس به دلایل مختلف و توسط افراد مختلف تغییر می‌کند.

همین موضوع آغاز بسیاری از مشکلات پروژه‌های بزرگ است.

هر اصل SOLID دقیقاً چه مشکلی را حل می‌کند؟

اصل مشکل راه‌حل
SRP چند مسئولیتی بودن کلاس‌ها تفکیک مسئولیت‌ها
OCP تغییر مداوم کلاس‌ها توسعه با ایجاد کلاس جدید
LSP وراثت نادرست جایگزینی صحیح کلاس‌ها
ISP Interfaceهای بزرگ تقسیم Interfaceها
DIP وابستگی مستقیم کلاس‌ها وابستگی به Interface

در ادامه مشاهده می‌کنید که این پنج اصل چگونه همین مشکلات را در فروشگاه اینترنتی ما برطرف می‌کنند.

مشکلات پروژه بدون SOLIDنمتهخهخ

اصل اول: Single Responsibility Principle (SRP)

اولین و شاید مهم‌ترین اصل SOLID بیان می‌کند هر کلاس باید فقط یک مسئولیت مشخص داشته باشد و تنها به یک دلیل تغییر کند.عبارت «یک دلیل برای تغییر» مهم‌ترین بخش این اصل است و بسیاری از توسعه‌دهندگان آن را با «کم بودن تعداد متدها» اشتباه می‌گیرند.

ممکن است کلاسی ده‌ها متد داشته باشد، اما همه آن‌ها یک مسئولیت را انجام دهند.در مقابل، کلاسی با سه متد نیز می‌تواند SRP را نقض کند، زیرا هر متد مسئولیت متفاوتی دارد.

بنابراین معیار اصلی، تعداد مسئولیت‌ها است، نه تعداد متدها.برای درک بهتر این اصل، همان فروشگاه اینترنتی را که ابتدای مقاله معرفی کردیم ادامه می‌دهیم.

در نسخه اولیه پروژه، تمام عملیات مربوط به سفارش داخل یک کلاس قرار گرفته است.

class OrderService
{
    public function create(OrderData $data): void
    {
        // Save Order
    }

    public function processPayment(Order $order): void
    {
        // Payment
    }

    public function sendConfirmationEmail(Order $order): void
    {
        // Send Email
    }

    public function sendConfirmationSms(Order $order): void
    {
        // Send SMS
    }

    public function generateInvoice(Order $order): void
    {
        // Create Invoice
    }
}

در نگاه اول این کلاس منظم به نظر می‌رسد، اما پنج مسئولیت مستقل را بر عهده دارد.

  • ثبت سفارش
  • پردازش پرداخت
  • ارسال ایمیل
  • ارسال پیامک
  • تولید فاکتور

هر یک از این بخش‌ها ممکن است توسط تیم متفاوتی توسعه داده شوند و در زمان‌های مختلف تغییر کنند.به عنوان مثال، اگر شرکت ارائه‌دهنده پیامک تغییر کند، فقط منطق ارسال پیامک باید ویرایش شود.

اما در این طراحی مجبور هستیم کلاس OrderService را تغییر دهیم.چند هفته بعد ممکن است واحد مالی ساختار فاکتور را تغییر دهد.

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

برای سومین بار همان کلاس تغییر خواهد کرد.

اکنون یک کلاس به دلایل مختلف تغییر می‌کند و دقیقاً همین موضوع اصل SRP را نقض می‌کند.

چرا این طراحی مشکل‌ساز است؟

فرض کنید توسعه‌دهنده‌ای در حال تغییر سیستم ارسال ایمیل است.

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

همچنین تست کردن این کلاس دشوار خواهد شد، زیرا برای بررسی هر بخش باید تمام وابستگی‌های دیگر نیز آماده باشند.

این دقیقاً همان مشکلی است که در بسیاری از پروژه‌های بزرگ مشاهده می‌شود.

پیاده‌سازی صحیح بر اساس SRP

در طراحی جدید، هر مسئولیت به یک کلاس مستقل منتقل می‌شود.

class OrderService
{
    public function create(OrderData $data): Order
    {
        // Save Order
    }
}

class PaymentService
{
    public function pay(Order $order): void
    {
        // Payment Logic
    }
}

class InvoiceService
{
    public function create(Order $order): void
    {
        // Generate Invoice
    }
}

class EmailNotificationService
{
    public function send(Order $order): void
    {
        // Send Email
    }
}

class SmsNotificationService
{
    public function send(Order $order): void
    {
        // Send SMS
    }
}

اکنون هر کلاس تنها یک مسئولیت مشخص دارد.

اگر سیستم پیامک تغییر کند، فقط SmsNotificationService ویرایش می‌شود.

اگر ساختار فاکتور تغییر کند، فقط InvoiceService تغییر خواهد کرد.

اگر روش پرداخت جدیدی اضافه شود، فقط PaymentService توسعه پیدا می‌کند.

هیچ‌یک از این تغییرات روی سایر بخش‌های سیستم تأثیر نخواهند گذاشت.

تحلیل مثال

در نسخه اول، OrderService مرکز تمام تغییرات پروژه بود.

هر قابلیت جدید یا هر تغییر کوچک باعث ویرایش همین کلاس می‌شد.به مرور زمان حجم کلاس افزایش پیدا می‌کرد و درک آن برای اعضای تیم دشوارتر می‌شد.

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

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

خلاصه اصل SRP

قبل از SRP بعد از SRP
کلاس‌های بزرگ کلاس‌های کوچک و متمرکز
چند دلیل برای تغییر فقط یک دلیل برای تغییر
تست دشوار تست ساده‌تر
وابستگی زیاد مسئولیت‌های مستقل
نگهداری پرهزینه نگهداری آسان‌تر

اصل SRP در پروژه واقعی

اکنون که هر مسئولیت در کلاس مستقل خود قرار گرفته است، می‌توانیم با خیال راحت قابلیت‌های جدید را به سیستم اضافه کنیم.

در بخش بعدی خواهید دید که چگونه اصل Open Closed Principle (OCP) به ما کمک می‌کند این توسعه را بدون تغییر کلاس‌های موجود انجام دهیم.

اصل دوم: Open Closed Principle (OCP)

در بخش قبل، مسئولیت‌های مختلف را از کلاس OrderService جدا کردیم و هر بخش در کلاس مستقل خود قرار گرفت.

اکنون ساختار پروژه خواناتر شده است، اما هنوز یک مشکل مهم وجود دارد.فرض کنید فروشگاه اینترنتی فقط از پرداخت کارت بانکی پشتیبانی می‌کند.

چند ماه بعد مدیر پروژه تصمیم می‌گیرد پرداخت با کیف پول و ZarinPal نیز به سیستم اضافه شود.

اگر هر بار برای اضافه شدن یک درگاه جدید مجبور شویم کلاس‌های قبلی را تغییر دهیم، به مرور احتمال ایجاد باگ افزایش پیدا خواهد کرد.

اینجاست که اصل Open Closed Principle وارد عمل می‌شود.

Open Closed Principle چیست؟

این اصل بیان می‌کند کلاس‌ها باید برای توسعه باز و برای تغییر بسته باشند.منظور این نیست که هیچ‌گاه نباید کدهای قبلی را ویرایش کنیم.

هدف این است که هنگام اضافه شدن قابلیت‌های جدید، تا حد امکان کلاس‌های پایدار پروژه بدون تغییر باقی بمانند.به عبارت دیگر، توسعه باید با افزودن کد جدید انجام شود، نه با تغییر کدهای قدیمی.

مثال نامناسب

فرض کنید PaymentService فقط یک کلاس ساده باشد.

class PaymentService
{
    public function pay(string $gateway)
    {
        if ($gateway === 'card') {

            // Card Payment

        } elseif ($gateway === 'wallet') {

            // Wallet Payment

        } elseif ($gateway === 'zarinpal') {

            // ZarinPal Payment

        }
    }
}

در نگاه اول این کد ساده است، اما هر بار که درگاه جدیدی اضافه شود باید همین کلاس تغییر کند.اگر فردا Stripe یا PayPal نیز به پروژه اضافه شوند، تعداد شرط‌ها بیشتر خواهد شد.

بعد از مدتی این کلاس به مجموعه‌ای از if و switch تبدیل می‌شود که نگهداری آن دشوار است.

هر بار که درگاه پرداخت جدیدی اضافه شود، کلاس PaymentService باید تغییر کند و این دقیقاً برخلاف هدف OCP است.

راه‌حل مناسب

به جای اینکه PaymentService تمام درگاه‌های پرداخت را بشناسد، یک Interface تعریف می‌کنیم.

interface PaymentGateway
{
    public function pay(Order $order): void;
}

اکنون هر روش پرداخت، کلاس مخصوص خود را خواهد داشت.

class CardPaymentGateway implements PaymentGateway
{
    public function pay(Order $order): void
    {
        // Card Payment
    }
}
class WalletPaymentGateway implements PaymentGateway
{
    public function pay(Order $order): void
    {
        // Wallet Payment
    }
}
class ZarinpalPaymentGateway implements PaymentGateway
{
    public function pay(Order $order): void
    {
        // ZarinPal Payment
    }
}

اکنون PaymentService دیگر مسئول اجرای منطق پرداخت نیست.

این کلاس فقط از یک درگاه پرداخت استفاده می‌کند و جزئیات پیاده‌سازی را نمی‌داند.

class PaymentService
{
    public function __construct(
        private PaymentGateway $gateway
    ) {
    }

    public function pay(Order $order): void
    {
        $this->gateway->pay($order);
    }
}

اضافه کردن یک درگاه جدید

چند ماه بعد مدیر پروژه تصمیم می‌گیرد پرداخت از طریق Stripe نیز اضافه شود.در نسخه قبلی باید PaymentService را تغییر می‌دادیم.اما اکنون فقط کافی است یک کلاس جدید ایجاد کنیم.

class StripePaymentGateway implements PaymentGateway
{
    public function pay(Order $order): void
    {
        // Stripe Payment
    }
}

همین.

هیچ تغییری در PaymentService ایجاد نشد.هیچ شرط جدیدی اضافه نشد.هیچ بخشی از کدهای پایدار پروژه ویرایش نشد.

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

تحلیل مثال

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

اما در طراحی دوم، PaymentService فقط با قرارداد PaymentGateway کار می‌کند.برای این کلاس تفاوتی ندارد که پرداخت توسط کارت بانکی انجام شود یا Stripe.

تنها چیزی که اهمیت دارد، پیاده‌سازی متد pay است.

در نتیجه سیستم برای توسعه باز و برای تغییر بسته خواهد بود. این دقیقاً مفهوم Open Closed Principle است.

ارتباط OCP با SRP

اگر SRP را رعایت نکرده بودیم، تمام منطق پرداخت هنوز داخل OrderService قرار داشت.در آن شرایط اجرای OCP تقریباً غیرممکن بود.

ابتدا مسئولیت پرداخت را جدا کردیم و سپس توانستیم آن را بدون تغییر توسعه دهیم.به همین دلیل اصول SOLID به یکدیگر وابسته هستند و معمولاً به صورت زنجیره‌ای پیاده‌سازی می‌شوند.

خلاصه اصل OCP

قبل از OCP بعد از OCP
استفاده از if و switch استفاده از Polymorphism
تغییر کلاس اصلی ایجاد کلاس جدید
وابستگی به پیاده‌سازی وابستگی به قرارداد
ریسک بالای ایجاد باگ توسعه با ریسک کمتر
نگهداری دشوار توسعه آسان‌تر

اصل OCP در فروشگاه اینترنتی

اکنون سیستم پرداخت ما بدون تغییر کلاس‌های موجود قابل توسعه است.اما هنوز یک سؤال مهم باقی مانده است.

آیا هر کلاسی که از PaymentGateway استفاده می‌کند، واقعاً می‌تواند جایگزین سایر پیاده‌سازی‌ها شود؟

پاسخ این سؤال ما را به اصل سوم SOLID یعنی Liskov Substitution Principle (LSP) می‌رساند.

اصل سوم: Liskov Substitution Principle (LSP)

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

ابتدا مسئولیت‌ها را از یکدیگر جدا کردیم و سپس امکان توسعه سیستم پرداخت را بدون تغییر کلاس‌های موجود فراهم کردیم.اما هنوز یک سؤال مهم باقی مانده است.

آیا هر کلاسی که PaymentGateway را پیاده‌سازی می‌کند، واقعاً می‌تواند جایگزین سایر درگاه‌های پرداخت شود؟

اگر پاسخ این سؤال منفی باشد، هرچند از Interface استفاده کرده‌ایم، اما طراحی ما هنوز صحیح نیست.اینجاست که اصل Liskov Substitution Principle اهمیت پیدا می‌کند.

LSP چیست؟

این اصل بیان می‌کند که اشیای کلاس فرزند باید بتوانند بدون ایجاد رفتار غیرمنتظره، جایگزین اشیای کلاس والد شوند.

به زبان ساده، اگر کدی با یک Interface یا کلاس والد کار می‌کند، نباید هنگام استفاده از کلاس فرزند دچار خطا یا رفتار متفاوت شود.

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

مثال نامناسب

فرض کنید تمام درگاه‌های پرداخت باید عملیات پرداخت را انجام دهند.

interface PaymentGateway
{
    public function pay(Order $order): void;
}

درگاه پرداخت کارت بانکی این قرارداد را به درستی پیاده‌سازی می‌کند.

class CardPaymentGateway implements PaymentGateway
{
    public function pay(Order $order): void
    {
        // Process Payment
    }
}

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

class CashPaymentGateway implements PaymentGateway
{
    public function pay(Order $order): void
    {
        throw new Exception('Cash payment cannot be processed online.');
    }
}

این کلاس قرارداد Interface را نقض می‌کند.

کدی که انتظار دارد متد pay عملیات پرداخت را انجام دهد، اکنون با Exception مواجه می‌شود.

در نتیجه CashPaymentGateway نمی‌تواند جایگزین سایر پیاده‌سازی‌ها شود.

این دقیقاً نقض اصل Liskov Substitution Principle (LSP) است.

چرا این طراحی اشتباه است؟

مشکل اصلی Interface نیست.مشکل این است که پرداخت نقدی رفتار متفاوتی نسبت به سایر روش‌های پرداخت دارد.در پرداخت آنلاین، مبلغ همان لحظه از مشتری دریافت می‌شود.

اما در پرداخت نقدی هیچ تراکنشی انجام نمی‌شود و مبلغ هنگام تحویل کالا پرداخت خواهد شد.بنابراین این دو رفتار، قرارداد یکسانی ندارند.

پیاده‌سازی صحیح

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

ابتدا Interface مربوط به پرداخت آنلاین را نگه می‌داریم.

interface OnlinePaymentGateway
{
    public function pay(Order $order): void;
}

اکنون تمام درگاه‌های آنلاین همین قرارداد را رعایت می‌کنند.

class CardPaymentGateway implements OnlinePaymentGateway
{
    public function pay(Order $order): void
    {
        // Card Payment
    }
}

class StripePaymentGateway implements OnlinePaymentGateway
{
    public function pay(Order $order): void
    {
        // Stripe Payment
    }
}

class ZarinpalPaymentGateway implements OnlinePaymentGateway
{
    public function pay(Order $order): void
    {
        // ZarinPal Payment
    }
}

برای پرداخت نقدی نیز مفهوم جداگانه‌ای تعریف می‌کنیم.

class CashOnDelivery
{
    public function confirm(Order $order): void
    {
        // پرداخت هنگام تحویل
    }
}

اکنون هیچ کلاسی مجبور نیست رفتاری را پیاده‌سازی کند که با ماهیت آن سازگار نیست.

تحلیل مثال

در نسخه اول، تمام روش‌های پرداخت مجبور بودند متد pay را پیاده‌سازی کنند.در حالی که پرداخت نقدی اساساً پرداخت آنلاین نیست.به همین دلیل یکی از کلاس‌ها رفتار غیرمنتظره‌ای داشت و Exception پرتاب می‌کرد.

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

هر جا که از این Interface استفاده شود، امکان جایگزینی کلاس‌ها بدون تغییر منطق برنامه وجود دارد.

این دقیقاً هدف Liskov Substitution Principle است.

ارتباط LSP با OCP

در اصل قبل یاد گرفتیم چگونه بدون تغییر کلاس‌های موجود، قابلیت جدید اضافه کنیم.اما اگر کلاس جدید قرارداد قبلی را رعایت نکند، توسعه سیستم باعث ایجاد خطا خواهد شد.

بنابراین OCP بدون رعایت LSP پایدار نخواهد بود.

ابتدا باید اطمینان داشته باشیم تمام پیاده‌سازی‌ها رفتار یکسانی دارند و سپس آن‌ها
را جایگزین یکدیگر کنیم.

خلاصه اصل LSP

قبل از LSP بعد از LSP
وراثت نادرست قراردادهای صحیح
رفتار غیرمنتظره رفتار قابل پیش‌بینی
Exceptionهای غیرضروری پیاده‌سازی صحیح قرارداد
عدم امکان جایگزینی جایگزینی بدون تغییر

اکنون ساختار پرداخت فروشگاه ما از نظر مسئولیت، توسعه‌پذیری و قراردادهای نرم‌افزاربهبود پیدا کرده است.اما هنوز یک مشکل دیگر وجود دارد.

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

این همان موضوعی است که در اصل چهارم،
Interface Segregation Principle (ISP)
بررسی خواهیم کرد.

اصل چهارم: Interface Segregation Principle (ISP)

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

به مرور زمان قابلیت‌های جدیدی به سیستم پرداخت اضافه می‌شوند و
Interface اصلی بزرگ‌تر و بزرگ‌تر می‌شود.

اینجاست که اصل Interface Segregation Principle اهمیت پیدا
می‌کند.

ISP چیست؟

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

مثال نامناسب

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

  • بازپرداخت مبلغ
  • لغو تراکنش
  • استعلام وضعیت پرداخت
  • تأیید پرداخت

توسعه‌دهنده تمام این قابلیت‌ها را داخل یک Interface قرار می‌دهد.

interface PaymentGateway
{
    public function pay(Order $order): void;

    public function refund(Order $order): void;

    public function verify(string $reference): bool;

    public function cancel(Order $order): void;
}

درگاه Stripe تمام این قابلیت‌ها را پشتیبانی می‌کند.اما درگاه پرداخت کارت بانکی فروشگاه فقط عملیات پرداخت را انجام می‌دهد.

در نتیجه توسعه‌دهنده مجبور می‌شود متدهایی را پیاده‌سازی کند که هیچ کاربردی
ندارند.

class CardPaymentGateway implements PaymentGateway
{
    public function pay(Order $order): void
    {
        // Process Payment
    }

    public function refund(Order $order): void
    {
        throw new LogicException();
    }

    public function verify(string $reference): bool
    {
        throw new LogicException();
    }

    public function cancel(Order $order): void
    {
        throw new LogicException();
    }
}

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

این دقیقاً نقض اصل ISP است.

چرا این طراحی مشکل‌ساز است؟

هر زمان Interface تغییر کند، تمام کلاس‌های پیاده‌سازی‌کننده نیز باید
تغییر کنند.

حتی اگر آن قابلیت هیچ ارتباطی با آن کلاس نداشته باشد.در پروژه‌های بزرگ ممکن است ده‌ها کلاس به یک Interface وابسته باشند.در چنین شرایطی کوچک‌ترین تغییر، بخش بزرگی از پروژه را تحت تأثیر قرار می‌دهد.

پیاده‌سازی صحیح

به جای یک Interface بزرگ، قراردادها را بر اساس مسئولیت تقسیم می‌کنیم.

interface PaymentGateway
{
    public function pay(Order $order): void;
}
interface RefundableGateway
{
    public function refund(Order $order): void;
}
interface VerifiableGateway
{
    public function verify(string $reference): bool;
}
interface CancelableGateway
{
    public function cancel(Order $order): void;
}

اکنون هر درگاه فقط Interfaceهای موردنیاز خود را پیاده‌سازی می‌کند.

class StripePaymentGateway implements
    PaymentGateway,
    RefundableGateway,
    VerifiableGateway,
    CancelableGateway
{
    // ...
}
class CardPaymentGateway implements PaymentGateway
{
    public function pay(Order $order): void
    {
        // Process Payment
    }
}

دیگر هیچ کلاسی مجبور به پیاده‌سازی متدهای غیرضروری نیست.

تحلیل مثال

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

این موضوع باعث افزایش کدهای اضافی و وابستگی‌های غیرضروری می‌شد.در نسخه دوم، هر قرارداد فقط یک مسئولیت مشخص دارد.

در نتیجه کلاس‌ها فقط به قابلیت‌هایی وابسته هستند که واقعاً از آن‌ها استفاده می‌کنند.

این طراحی خواناتر، توسعه‌پذیرتر و قابل نگهداری‌تر است.

ارتباط ISP با سه اصل قبلی

در SRP مسئولیت کلاس‌ها را جدا کردیم.

در OCP توسعه سیستم را بدون تغییر کلاس‌های موجود انجام دادیم.

در LSP اطمینان پیدا کردیم که تمام پیاده‌سازی‌ها قرارداد خود را رعایت
می‌کنند.

اکنون در ISP همان تفکیک مسئولیت را برای
Interfaceها نیز انجام می‌دهیم.

به همین دلیل بسیاری از توسعه‌دهندگان ISP را نسخه
SRP برای Interfaceها می‌دانند.

خلاصه اصل ISP

قبل از ISP بعد از ISP
Interface بزرگ Interfaceهای کوچک
متدهای اضافی قراردادهای تخصصی
وابستگی غیرضروری وابستگی حداقلی
کدهای بدون استفاده پیاده‌سازی هدفمند

اصل ISP در سیستم پرداخت

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

با وجود تمام این بهبودها، PaymentService هنوز مستقیماً به یک پیاده‌سازی مشخص از PaymentGateway وابسته است.

در بخش بعدی با آخرین اصل SOLID یعنی
Dependency Inversion Principle (DIP)
این وابستگی را نیز حذف خواهیم کرد.

اصل پنجم: Dependency Inversion Principle (DIP)

چهار اصل قبلی ساختار فروشگاه اینترنتی ما را تا حد زیادی بهبود دادند.

کلاس‌ها تنها یک مسئولیت دارند، توسعه سیستم بدون تغییر انجام می‌شود، قراردادهارعایت شده‌اند و Interfaceهای بزرگ نیز به بخش‌های کوچک‌تر تقسیم
شده‌اند.

اما هنوز یک وابستگی مهم در پروژه وجود دارد.

اگر PaymentService مستقیماً یک درگاه پرداخت را ایجاد کند، تغییر آن در آینده دوباره باعث ویرایش کلاس خواهد شد.

آخرین اصل SOLID دقیقاً برای حل همین مشکل ایجاد شده است.

Dependency Inversion Principle چیست؟

این اصل بیان می‌کند کلاس‌های سطح بالا نباید به کلاس‌های سطح پایین وابسته باشند.

هر دو باید به Abstraction یا همان Interface وابسته باشند.

همچنین جزئیات پیاده‌سازی نباید منطق اصلی برنامه را کنترل کنند.

در واقع Interface باید تعیین کند سیستم چگونه کار می‌کند و کلاس‌های
مختلف فقط آن قرارداد را پیاده‌سازی کنند.

مثال نامناسب

فرض کنید PaymentService خودش درگاه پرداخت را ایجاد می‌کند.

class PaymentService
{
    private CardPaymentGateway $gateway;

    public function __construct()
    {
        $this->gateway = new CardPaymentGateway();
    }

    public function pay(Order $order): void
    {
        $this->gateway->pay($order);
    }
}

در این طراحی PaymentService فقط با
CardPaymentGateway کار می‌کند.

اگر فردا بخواهیم Stripe یا ZarinPal را جایگزین کنیم،
باید همین کلاس را تغییر دهیم.

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

این دقیقاً برخلاف اصل DIP است.

چرا این طراحی مشکل دارد؟

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

وقتی کلاس سطح بالا از این جزئیات اطلاع داشته باشد، هر تغییر کوچکی باعث تغییر
منطق اصلی برنامه خواهد شد.

پیاده‌سازی صحیح

ابتدا PaymentService را به Interface وابسته می‌کنیم.

class PaymentService
{
    public function __construct(
        private PaymentGateway $gateway
    ) {
    }

    public function pay(Order $order): void
    {
        $this->gateway->pay($order);
    }
}

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

استفاده در پروژه

$paymentService = new PaymentService(
    new StripePaymentGateway()
);

$paymentService->pay($order);

اگر مدیر پروژه تصمیم بگیرد از زرین‌پال استفاده کند، فقط شیء تزریق‌شده تغییر
خواهد کرد.

$paymentService = new PaymentService(
    new ZarinpalPaymentGateway()
);

$paymentService->pay($order);

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

$paymentService = new PaymentService(
    new CardPaymentGateway()
);

$paymentService->pay($order);

همان PaymentService بدون هیچ تغییری با تمام این درگاه‌ها کار می‌کند.

نقش Dependency Injection

شاید هنگام مشاهده مثال قبلی این سؤال برای شما ایجاد شده باشد که چه کسی شیء
PaymentGateway را ایجاد می‌کند.

پاسخ این سؤال، Dependency Injection است.

در Dependency Injection وابستگی‌ها از بیرون کلاس ایجاد و به آن تزریق می‌شوند.بنابراین کلاس فقط از آن‌ها استفاده می‌کند و مسئول ساختن آن‌ها نیست.

در Laravel این وظیفه توسط Service Container انجام می‌شود.به همین دلیل بسیاری از قابلیت‌های Laravel بر پایه اصل DIP طراحی شده‌اند.

تحلیل مثال

در نسخه اول، PaymentService مسئول ایجاد CardPaymentGateway بود.به همین دلیل هر تغییر در سیستم پرداخت، این کلاس را نیز تحت تأثیر قرار می‌داد.

در نسخه دوم، PaymentService فقط به قرارداد PaymentGateway وابسته است.اکنون هر درگاه جدید بدون تغییر منطق برنامه قابل استفاده خواهد بود.

همین موضوع باعث افزایش تست‌پذیری، توسعه‌پذیری و انعطاف سیستم می‌شود.

ارتباط DIP با چهار اصل قبلی

SRP مسئولیت‌ها را از یکدیگر جدا کرد.

OCP امکان توسعه بدون تغییر را فراهم کرد.

LSP اطمینان داد تمام پیاده‌سازی‌ها رفتار یکسانی دارند.

ISP قراردادهای بزرگ را به Interfaceهای کوچک‌تر تقسیم کرد.

اکنون DIP تمام این بخش‌ها را به کمک Abstraction به یکدیگر متصل می‌کند.

به همین دلیل بسیاری از معماران نرم‌افزار، DIP را مهم‌ترین اصل SOLID می‌دانند.

خلاصه اصل DIP

قبل از DIP بعد از DIP
وابستگی به کلاس وابستگی به Interface
ایجاد وابستگی داخل کلاس تزریق وابستگی از بیرون
انعطاف‌پذیری کم انعطاف‌پذیری بالا
تست دشوار تست آسان
تغییرات پرهزینه تغییرات کم‌هزینه

اصل DIP در فروشگاه اینترنتی

اکنون معماری فروشگاه اینترنتی ما هر پنج اصل SOLID را رعایت می‌کند.

در بخش بعدی بررسی می‌کنیم این پنج اصل چگونه در کنار یکدیگر کار می‌کنند و چرا بیشتر Design Patternها و معماری‌های مدرن بر پایه همین اصول ساخته شده‌اند.

ارتباط پنج اصل SOLID با یکدیگر

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

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

اصل مشکل نتیجه
SRP کلاس‌های چند مسئولیتی تفکیک مسئولیت‌ها
OCP تغییر مداوم کلاس‌ها توسعه بدون تغییر
LSP وراثت نادرست جایگزینی صحیح کلاس‌ها
ISP Interfaceهای بزرگ قراردادهای کوچک و تخصصی
DIP وابستگی مستقیم وابستگی به Abstraction

اگر حتی یکی از این اصول رعایت نشود، کیفیت طراحی کاهش پیدا می‌کند.

برای مثال اگر OCP را رعایت کنیم اما DIP را نادیده بگیریم، کلاس‌های اصلیهمچنان به پیاده‌سازی‌های مشخص وابسته خواهند بود.

همچنین اگر SRP رعایت نشود، اجرای OCP و DIP نیز بسیار دشوار خواهد شد.به همین دلیل این پنج اصل را باید یک مجموعه واحد در نظر گرفت، نه پنج قانون
مستقل.

ارتباط SOLID با Design Pattern

یکی از پرسش‌های رایج این است که آیا بعد از یادگیری SOLID باید Design Patternها را مطالعه کنیم یا این دو موضوع مستقل هستند.

پاسخ کوتاه این است که Design Patternها در بسیاری از موارد برای پیاده‌سازی عملی اصول SOLID ایجاد شده‌اند.

SOLID به شما می‌گوید یک سیستم چگونه باید طراحی شودوDesign Patternها نشان می‌دهند این طراحی را چگونه در عمل پیاده‌سازی کنید.

به عبارت دیگر، SOLID «اصل» است و Design Pattern «راه‌حل».

اصل SOLID Design Patternهای مرتبط
SRP Facade، Command
OCP Strategy، Factory Method
LSP Template Method، Strategy
ISP Adapter، Bridge
DIP Dependency Injection، Factory

به همین دلیل بسیاری از برنامه‌نویسان ابتدا SOLID را یاد می‌گیرند و سپس سراغ
Design Patternها می‌روند.

در این حالت دلیل استفاده از هر الگوی طراحی را نیز بهتر درک خواهند کرد.

ارتباط SOLID با Clean Architecture

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

برای مثال تغییر پایگاه داده، Framework یا سرویس پیامک نباید باعث تغییر قوانین
کسب‌وکار شود.

رسیدن به چنین ساختاری بدون استفاده از SRP، OCP و DIP تقریباً غیرممکن است. به همین دلیل SOLID را می‌توان یکی از پایه‌های اصلی Clean Architecture دانست.

SOLID در Laravel

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

اگر کدهای هسته Laravel را بررسی کنید، نمونه‌های فراوانی از استفاده اصول SOLID را
مشاهده خواهید کرد.

قابلیت Laravel اصل مرتبط توضیح
Service Container DIP تزریق وابستگی‌ها
Contracts ISP تعریف قراردادها
Filesystem OCP افزودن Driver جدید
Cache Drivers OCP + DIP تعویض Driver بدون تغییر کد
Notification Channels OCP افزودن کانال جدید
Queue Drivers DIP وابستگی به Interface

به عنوان نمونه، هنگام استفاده از Cache در Laravel معمولاً اهمیتی ندارد که داده‌هادر Redis، Database یا File ذخیره شوند.

برنامه فقط با قرارداد Cache کار می‌کند و جزئیات پیاده‌سازی توسط Framework مدیریت می‌شوند.

این دقیقاً همان چیزی است که در اصل Dependency Inversion بررسی کردیم.

نتیجه استفاده از SOLID در پروژه فروشگاه

بیایید دوباره به فروشگاه اینترنتی ابتدای مقاله برگردیم.در ابتدای مسیر، تمام منطق سیستم داخل چند کلاس بزرگ قرار داشت.

هر قابلیت جدید باعث تغییر همان کلاس‌ها می‌شد و توسعه پروژه روزبه‌روز دشوارتر می‌شد.

اکنون وضعیت پروژه کاملاً متفاوت است.

  • هر کلاس فقط یک مسئولیت دارد.
  • افزودن درگاه پرداخت جدید بدون تغییر کدهای قبلی انجام می‌شود.
  • تمام پیاده‌سازی‌ها قراردادهای یکسانی دارند.
  • Interfaceها کوچک و تخصصی هستند.
  • وابستگی‌ها از طریق Interface مدیریت می‌شوند.

نتیجه این تغییرات فقط زیباتر شدن کد نیست.

پروژه راحت‌تر توسعه پیدا می‌کند، تست‌ها ساده‌تر نوشته می‌شوند و هزینه نگهداری
سیستم نیز کاهش می‌یابد.

این همان هدفی است که SOLID از ابتدا دنبال می‌کرد.
تکامل پروژه با استفاده از SOLID

اشتباهات رایج هنگام استفاده از SOLID

یادگیری SOLID به معنای استفاده از آن در تمام بخش‌های پروژه نیست.

بسیاری از برنامه‌نویسان پس از آشنایی با این اصول، سعی می‌کنند آن‌ها را در هر
کلاسی پیاده‌سازی کنند.

این کار معمولاً باعث افزایش پیچیدگی پروژه می‌شود.

در ادامه، چند مورد از رایج‌ترین اشتباهات را بررسی می‌کنیم.


۱. ایجاد Interface برای تمام کلاس‌ها

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

Interface زمانی مفید است که چند پیاده‌سازی مختلف یا نیاز به جداسازیوابستگی‌ها وجود داشته باشد.

۲. شکستن بیش از اندازه کلاس‌ها

SRP به معنی کوچک کردن بی‌دلیل کلاس‌ها نیست.اگر هر متد را به یک کلاس جداگانه منتقل کنید، پروژه بیش از اندازه پیچیده خواهد شد.

هدف SRP تفکیک مسئولیت است، نه افزایش تعداد فایل‌ها.


۳. استفاده نادرست از وراثت

وراثت همیشه بهترین انتخاب نیست.اگر رابطه واقعی «یک نوع از» بین دو کلاس وجود نداشته باشد،Composition انتخاب مناسب‌تری خواهد بود.این موضوع به رعایت بهتر اصل LSP نیز کمک می‌کند.

۴. Interfaceهای بسیار بزرگ

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


۵. وابستگی مستقیم به کلاس‌ها

اگر Serviceهای اصلی پروژه مستقیماً کلاس‌هایConcrete را ایجاد کنند، انعطاف سیستم کاهش پیدا می‌کند.استفاده از Abstraction و Dependency Injection این مشکل
را برطرف می‌کند.

چه زمانی از SOLID استفاده کنیم؟

رعایت SOLID تقریباً در تمام پروژه‌های متوسط و بزرگ توصیه می‌شود.هرچه پروژه بزرگ‌تر باشد، مزایای این اصول بیشتر نمایان می‌شوند.

نوع پروژه استفاده از SOLID
اسکریپت کوتاه ضرورت کمی دارد.
وب‌سایت شرکتی در بخش‌های اصلی توصیه می‌شود.
فروشگاه اینترنتی کاملاً توصیه می‌شود.
سیستم سازمانی ضروری است.
SaaS و پروژه‌های بلندمدت ضروری است.

هرچه تعداد توسعه‌دهندگان و طول عمر پروژه بیشتر باشد، اهمیتSOLID نیز افزایش پیدا می‌کند.

چه زمانی نباید از SOLID استفاده کنیم؟

SOLID مجموعه‌ای از اصول طراحی است، نه قوانینی که باید در تمامشرایط بدون استثنا اجرا شوند.

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

۱. پروژه‌های کوچک و کوتاه‌مدت

در یک اسکریپت ساده یا پروژه‌ای که فقط چند فایل محدود دارد، ایجاد چندینInterface و کلاس جداگانه ممکن است ارزش واقعی ایجاد نکند.

در این شرایط یک طراحی ساده‌تر معمولاً خواناتر و قابل فهم‌تر خواهد بود.

۲. نمونه اولیه یا Prototype

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

۳. زمانی که Abstraction اضافه ایجاد می‌کند

هر Interface یا لایه اضافی یک هزینه نگهداری دارد.اگر احتمال تغییر یک بخش بسیار کم است، ایجاد یک Abstraction فقط برای
رعایت ظاهری SOLID می‌تواند باعث پیچیدگی غیرضروری شود.

۴. وقتی سادگی ارزش بیشتری دارد

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

یک توسعه‌دهنده حرفه‌ای بین سادگی کد و انعطاف‌پذیری آینده تعادل برقرار می‌کند.

هدف SOLID ساختن کد پیچیده‌تر نیست؛ هدف آن ساختن کدی است که در برابر
تغییرات آینده مقاومت بیشتری داشته باشد.

جمع‌بندی

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

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

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

همچنین مشاهده کردیم که Frameworkهایی مانند Laravel وبسیاری از Design Patternها بر پایه همین اصول طراحی شده‌اند.

اگر قصد دارید معماری نرم‌افزار را به صورت حرفه‌ای یاد بگیرید، تسلط برSOLID یکی از مهم‌ترین قدم‌های مسیر شما خواهد بود.

سوالات متداول

SOLID چیست؟

SOLID مجموعه‌ای از پنج اصل طراحی نرم‌افزار است که به توسعه کدهایخواناتر، توسعه‌پذیرتر و قابل نگهداری کمک می‌کند.


آیا SOLID فقط برای زبان PHP است؟

خیر. این اصول به زبان خاصی وابسته نیستند و در تمام زبان‌های شی‌گرا مانند
Java، C#، Kotlin،
TypeScript و PHP کاربرد دارند.


آیا Laravel از SOLID استفاده می‌کند؟

بله. بخش‌هایی مانند Service Container،Contracts، Cache، Queue وNotification نمونه‌هایی از استفاده اصول SOLID درLaravel هستند.


تفاوت SOLID و Design Pattern چیست؟

SOLID مجموعه‌ای از اصول طراحی است، اماDesign Patternها راه‌حل‌هایی برای پیاده‌سازی این اصول در مسائلمختلف هستند.


یادگیری SOLID قبل از Design Pattern ضروری است؟

بله. درک SOLID باعث می‌شود دلیل استفاده از هرDesign Pattern را بهتر متوجه شوید و از آن‌ها در جای مناسب استفاده
کنید.

گام بعدی

اکنون که با اصول SOLID آشنا شدید، زمان مناسبی است تا Design Patternها را نیز یاد بگیرید.در مقاله‌های بعدی، هر یک از الگوهای طراحی را به صورت عملی بررسی می‌کنیم و
خواهیم دید چگونه این الگوها به پیاده‌سازی بهتر اصول SOLID کمک می‌کنند.


نتیجه‌گیری

کدنویسی حرفه‌ای تنها به نوشتن کدهای درست محدود نمی‌شود. یک توسعه‌دهنده حرفه‌ای کدی می‌نویسد که ماه‌ها یا حتی سال‌ها بعد نیز بتوان آن رابه‌راحتی توسعه داد و نگهداری کرد.
SOLID یکی از مهم‌ترین ابزارهایی است که رسیدن به این هدف را ممکن می‌کند.

[extrnalreadmorebox]Martin Fowler
Laravel Documentation
PHP Manual
[/extrnalreadmorebox]

پرسش و پاسخ

اولین دیدگاه رو شما برای ما ارسال و کنید

کاربر گرامی : برای ارسال دیدگاه خود ابتدا باید در سایت لاگین نمایید.