danny
danny
خواندن ۱۳ دقیقه·۱ ماه پیش

اصول SOLID یک بار برای همیشه

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


مقدمه:

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

حروف اختصاری SOLID مخفف موارد زیر است:

  • اصل مسئولیت واحد Single Responsibility Principle (SRP)
  • اصل باز و بسته Open/Closed Principle (OCP)
  • اصل جانشینی Liskov یا Liskov Substitution Principle (LSP)
  • اصل جداسازی رابط‌ها Interface Segregation Principle (ISP)
  • اصل وارونگی وابستگی Dependency Inversion Principle (DIP)


اهمیت اصول طراحی در توسعه نرم‌افزار

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

  • قابلیت نگهداری: رعایت اصول طراحی اصولی باعث می‌شود کد قابل نگهداری‌تر شود. هنگامی که کد به خوبی ساختاریافته باشد و به این اصول پایبند باشد، شناسایی و رفع مشکلات، اضافه‌کردن ویژگی‌های جدید و انجام بهبودها بدون ایجاد عواقب ناخواسته آسان‌تر می‌شود.
  • قابلیت مقیاس‌پذیری: نرم‌افزارهای طراحی شده به‌خوبی مقیاس‌پذیر هستند. آنها می‌توانند بدون نیاز به بازنگری گسترده یا پیچیده‌شدن تدریجی کد برنامه، تغییرات و رشد در نیازمندی‌های جدید را پذیرا باشند. قابلیت استفاده مجدد از کد: رعایت اصول طراحی اغلب منجر به کدی می‌شود که قابلیت استفاده مجدد بیشتری دارد. اجزای قابل‌استفاده مجدد باعث صرفه‌جویی در زمان و تلاش مؤثرتر در توسعه و تست می‌شوند.
  • همکاری: اصول طراحی یک چارچوب مشترک برای توسعه‌دهندگان برای کار در آن ارائه می‌دهد. این درک مشترک باعث همکاری و کاهش سوءتفاهم بین اعضای تیم می‌شود.
  • کاهش باگ‌ها و مشکلات: رعایت اصول طراحی به شناسایی و کاهش مشکلات رایج برنامه‌نویسی و دام‌های طراحی کمک می‌کند. این منجر به باگ‌های کمتر و نرم‌افزار باثبات‌تر می‌شود.
  • آینده‌نگری: نرم‌افزارهای طراحی شده به‌خوبی می‌توانند با نیازها و فناوری‌های در حال تغییر سازگار شوند. این سرمایه‌گذاری برای ماندگاری بلندمدت محصول نرم‌افزار است. حال بیایید به طور عمیق به تک‌تک اجزای اصول SOLID بپردازیم.

اصل مسئولیت واحد (SRP)

حرف "S" در اصول SOLID مخفف Single Responsibility Principle (SRP) است که بیان می‌کند یک کلاس باید فقط یک دلیل برای تغییر داشته باشد، یا به‌عبارت‌دیگر، باید یک مسئولیت یا کار مشخص و خوبی تعریف شده در یک سیستم نرم‌افزاری داشته باشد.

بیایید نگاهی به یک نمونه کد جاوا در زیر بیندازیم که به طور واضح اصل SRP را نقض می‌کند:

SRP - bad
SRP - bad

در مثال بالا، کلاس کارمند (Employee) دو مسئولیت دارد: محاسبه حقوق کارمند و ایجاد گزارش حقوق و دستمزد. این کار اصل مسئولیت واحد (SRP) را نقض می‌کند؛ زیرا دلیل بیشتری برای تغییر دارد.

رفع نقض اصل SRP

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

SRP - good
SRP - good

در راه‌حل بازنگری شده، مسئولیت‌های محاسبه حقوق و ایجاد گزارش حقوق و دستمزد تقسیم شده است که هر PayrollReportGenerator و Employee به دو کلاس مجزا کدام یک مسئولیت واحد دارند. این با اصل SRP مطابقت دارد. بیایید نگاهی به نمایش کلاس‌ها و پیاده‌سازی SRP بیندازیم.

 SRP - compare
SRP - compare

خطاها و اشتباه‌های رایج

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

  • همان‌طور که در مثال اولیه نشان داده شد، سربارگذاری (overloading) بیش از حد کلاس‌ها با مسئولیت‌های متعدد یک اشتباه رایج است. این می‌تواند منجر به کد پیچیده و نگهداری سخت شود.
  • نادیده‌گرفتن اصل انسجام (Cohesion) که به SRP مرتبط است، می‌تواند منجر به کلاس‌هایی شود که وظایف نامرتبطی را انجام می‌دهند و باعث شود کد سازماندهی کمتری داشته باشد و مدیریت آن دشوارتر شود.

بهترین راه‌حل‌ها:

برای رعایت مؤثر،SRP نکات زیر را در نظر بگیرید:

مسئولیت‌ها یا نقش‌هایی را که هر کلاس باید داشته باشد به طور واضح تعریف کنید. از مخلوط‌کردن وظایف نامرتبط در یک کلاس واحد خودداری کنید. از اصل جداسازی نگرانی‌ها (SoC(Separation of Concerns برای تقسیم عملکرد برنامهٔ خود به ماژول‌ها یا کلاس‌های مجزا استفاده کنید که هر کدام مسئول یک دغدغه خاص هستند.

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

اصل باز/بسته (OCP)

اصل باز/بسته (Open/Closed Principle)OCP بیان می‌کند که موجودیت‌های نرم‌افزار (مانند کلاس‌ها، ماژول‌ها و توابع) باید برای توسعه باز باشند؛ اما برای تغییر بسته باشند. به‌عبارت‌دیگر، شما باید بتوانید بدون تغییر کد منبع موجود، قابلیت یا رفتار جدیدی را به یک موجودیت نرم‌افزار اضافه کنید. برای درک بهتر اصل باز/بسته (OCP) بیایید نگاهی به یک نمونه کد جاوا بیندازیم که به طور واضح این اصل را نقض می‌کند.

 OCP -bad
OCP -bad

در مثال بالا، کلاس مستطیل (Rectangle) برای محاسبه مساحت یک مستطیل طراحی شده است. بااین‌حال، اگر می‌خواهید آن را برای کار با اشکال دیگر مانند دایره یا مثلث گسترش دهید، باید کلاس را اصلاح کنید که این کار اصل باز/بسته (OCP) را نقض می‌کند.

رفع نقض اصل باز/بسته (OCP)

ما اصل باز/بسته (OCP) را اعمال خواهیم کرد و خواهیم دید که چگونه می‌توانیم مشکل ذکر شده در بالا را حل کنیم.

 OCP -good
OCP -good


در این راه‌حل بازنگری شده، کلاس Shape به‌عنوان یک کلاس پایه انتزاعی برای همه اشکال معرفی شده است. هر شکل (Shape ) خاص (Rectangle, Circle) کلاس Shape را گسترش می‌دهد و پیاده‌سازی خاص خود از متد calculateArea را ارائه می‌دهد. این با اصل باز/بسته (OCP) مطابقت دارد؛ زیرا شما می‌توانید بدون تغییر کد موجود، اشکال جدید اضافه کنید. در زیر نمایش بصری کلاس ها و پیاده‌سازی اصل باز/بسته (OCP) آمده است.

OCP-good
OCP-good

خطاها و اشتباهات رایج

بیایید برخی از خطاها و اشتباهات رایجی را که توسعه‌دهندگان ممکن است با آنها مواجه شوند بررسی کنیم:

  • یک اشتباه رایج، تغییر مستقیم کد موجود برای اضافه‌کردن عملکرد یا functionality جدید به‌جای گسترش آن است. این کار اصل باز/بسته (OCP) را نقض می‌کند و می‌تواند باعث ایجاد باگ یا مسیر اشتباه شود.
  • عدم تعریف انتزاع‌های مناسب یا کلاس‌های پایه می‌تواند منجر به کدی شود که بدون تغییر کد آن، گسترش و توسعه آن دشوار است.

بهترین راه‌حل‌ها:

برای پیاده‌سازی مؤثر اصل باز/بسته (OCP) باید نکات زیر را در نظر بگیرید:

  • برای ایجاد انتزاعی که موجودیت‌های نرم‌افزار می‌توانند برای ارائه قابلیت‌های جدید آنها را گسترش دهند یا پیاده‌سازی کنند از کلاس های انتزاعی (abstract classes) یا رابط‌ها (interfaces) استفاده کنید.
  • هنگام گسترش یک abstract class یا پیاده‌سازی یک، interface برای ارائه رفتار خاص برای هر موجودیت، متدها را override کنید.
  • از چندریختی (Polymorphism) برای رفتار یکنواخت با اشیا کلاس‌های Concrete مختلف (به‌عنوان‌مثال، اشکال مختلف) از طریق کلاس پایه یا رابط مشترک آنها استفاده کنید.
  • از تزریق وابستگی (Dependency Injection) برای تزریق وابستگی‌ها یا رفتارها به کلاس‌ها به‌جای hardcoding کردن آنها استفاده کنید تا گسترش و توسعه عملکرد آسان‌تر شود.

اصل جانشینی (LSP) Liskov

اصل Liskov Substitution Principle  یا به‌اختصار (LSP) بیان می‌کند که اشیا یک کلاس مشتق شده (subtype) باید بتوانند بدون تأثیر بر عملکرد برنامه، اشیای کلاس پایه (supertype) را جایگزین کنند. به‌عبارت‌دیگر، اگر کلاس A زیرمجموعه‌ای از کلاس B باشد، نمونه‌های کلاس B باید بدون ایجاد مشکل جایگزین نمونه‌های کلاس A شوند.

نمونه‌ای از نقض اصل جانشینی Liskov

برای درک بهتر اصل جانشینی Liskov بیایید نگاهی به یک نمونه کد جاوا بیندازیم که به طور واضح این اصل را نقض می‌کند.

LSP - bad
LSP - bad

در این مثال، کلاس شترمرغ (Ostrich) زیرمجموعه‌ای از کلاس پرنده (Bird) است. بااین‌حال، اصل جانشینی LSP را نقض می‌کند؛ زیرا متد fly را که ارتباطی با شترمرغ ندارد (زیرا شترمرغ نمی‌تواند پرواز کند) را override می‌کند. این بدان معنی است که نمی‌توان یک نمونه از شترمرغ را بدون ایجاد رفتار غیرمنتظره جایگزین نمونه‌ای از پرنده کرد.

رفع نقض LSP

ما اصل LSP را اعمال خواهیم کرد و خواهیم دید که چگونه می‌توانیم مشکل ذکر شده در بالا را حل کنیم:

LSP - good
LSP - good

در این مثال اصلاح شده، کلاس شترمرغ زیرمجموعه‌ای از کلاس پرنده (Bird) است و متد move را برای ارائه رفتاری معنادار که با اصل LSP مطابقت دارد را override می‌کند. یک نمونه از شترمرغ را می‌توان بدون ایجاد مشکل جایگزین نمونه‌ای از کلاس پرنده کرد. در اینجا نمایش بصری کلاس‌ها و پیاده‌سازی اصل LSP آمده است.

LSP
LSP

خطاها و اشتباهات رایج

  • همان‌طور که اصل LSP را یاد می‌گیریم، آگاهی از خطاها و اشتباهات رایجی که توسعه‌دهندگان ممکن است با آنها مواجه شوند، ضروری است. بیایید برخی از این مشکلات بالقوه را بررسی کنیم.
  • یک اشتباه رایج، override کردن متدها در کلاس های مشتق شده به روشی است که با رفتار کلاس پایه در تضاد باشد. تغییر پیش‌شرط‌ها (نیازمندی‌های ورودی) یا پس‌شرط‌ها (ضمانت‌های خروجی) متدها در کلاس‌های مشتق شده می‌تواند اصل جانشینی Liskov را نقض کند.

بهترین راه‌حل‌ها

برای پیاده‌سازی مؤثر اصل جانشینی، Liskov نکات زیر را در نظر بگیرید:

  • اطمینان حاصل کنید که کلاس های مشتق شده، سازگاری رفتاری کلاس های پایه خود را حفظ می‌کنند. متدها در کلاس های مشتق شده باید از همان قراردادهای متدهای کلاس پایه پیروی کنند.
  • اگر یک متد کلاس پایه پیش‌شرط‌های خاصی (به‌عنوان‌مثال، محدودیت‌های ورودی) داشته باشد، متدهای کلاس مشتق شده باید پیش‌شرط‌های مشابه یا ضعیف‌تری داشته باشند. پس‌شرط‌ها (ضمانت‌های خروجی) باید به‌اندازه متدهای کلاس پایه یا بهتر از آنها باشد.
  • از چندریختی(polymorphism) برای فعال‌کردن جایگزینی اشیای کلاس مشتق شده با اشیا کلاس پایه استفاده کنید. این اغلب شامل override کردن متدها برای ارائه رفتار مخصوص آنها درعین‌حال حفظ عملکرد اصلی است.

اصل جداسازی رابط (ISP)

حرف "I" در حروف اختصاری SOLID مخفف Interface Segregation Principle (ISP) است که بر این نکته تأکید دارد که سرویس کلاینت‌ها کلاس‌ها یا مؤلفه‌هایی که از رابط‌ها (interfaces) استفاده می‌کنند، نباید مجبور شوند به رابط‌هایی که استفاده نمی‌کنند وابسته باشند. به‌عبارت‌دیگر، یک رابط باید مجموعه خاصی و متمرکزی از متدها داشته باشد که به کلاسهای پیاده‌سازی شده مرتبط هستند.


نمونه‌ای از نقض اصل جداسازی رابط (ISP)

بیایید یک نمونه کد جاوا را بررسی کنیم که به طور واضح این اصل را نقض می‌کند:

ISP-bad
ISP-bad

در این مثال، اینترفیس Worker حاوی سه متد ()eat و () work و ()sleep است. بااین‌حال، ممکن است همه کلاس‌هایی که این اینترفیس‌هایی را پیاده‌سازی می‌کنند به همه این متدها نیاز نداشته باشند. این کار می‌تواند منجر به این شود که کلاس‌ها مجبور به پیاده‌سازی متدهایی شوند که برای عملکرد آنها بی‌ربط هستند و اصل جداسازی رابط (ISP) را نقض می‌کنند.

رفع نقض اصل جداسازی رابط (ISP)

ما اصل جداسازی رابط (ISP) را اعمال خواهیم کرد و خواهیم دید که چگونه می‌توانیم مشکل ذکر شده در بالا را حل کنیم.

ISP-good
ISP-good

در این راه‌حل بازنگری شده، رابط Worker به سه اینترفیس کوچک‌تر و متمرکزتر تقسیم شده است: قابل‌اجرا (Workable) قابل‌خوردن (Eatable) و قابل خواب (Sleepable) هستند. حال، کلاس‌ها می‌توانند فقط اینترفیس‌هایی را که با عملکرد آنها مرتبط هستند را پیاده‌سازی کنند که مطابق با اصل جداسازی رابط (ISP) است.

نمایش بصری اینترفیس‌های بازنگری شده (Sleepable ،Eatable ،Workable) مطابق با اصل جداسازی رابط (ISP) در زیر نشان‌داده‌شده است.

خطاها و اشتباهات رایج

بیایید برخی از خطاها و اشتباهات رایجی را که توسعه‌دهندگان هنگام اعمال اصل جداسازی رابط (ISP) ممکن است با آنها مواجه شوند، بررسی کنیم:

ایجاد رابط‌های بزرگ و یکپارچه با روش‌های زیاد می‌تواند منجر به پیاده‌سازی متدهایی در کلاس‌ها شود که به آنها نیاز ندارند و این امر اصل جداسازی رابط (ISP) را نقض می‌کند. تغییر یک اینترفیس برای اضافه یا حذف متدها می‌تواند پیاده‌سازی‌های موجود را بشکند. هنگام ایجاد تغییرات، توجه به تأثیر آن بر سرویس‌گیرنده‌ها Client مهم است.

بهترین راه‌حل‌ها

برای رعایت مؤثر اصل جداسازی رابط (ISP) نکات زیر را در نظر بگیرید:

  • اینترفیس‌ها را با یک هدف خاص طراحی کنید که فقط شامل متدهایی باشد که به طور مستقیم به آن هدف مرتبط هستند.
  • ترجیحاً به‌جای یک اینترفیس بزرگ که همه کاری می‌کند، چندین اینترفیس کوچک‌تر داشته باشید که سرویس کلاینت بتواند پیاده‌سازی آنها را انتخاب کند.
  • از دیدگاه سرویس کلاینت‌ها (کلاس‌هایی که اینترفیس را پیاده‌سازی می‌کنند) فکر کنید و اینترفیس‌هایی را در اختیار آنها قرار دهید که متناسب با نیازهایشان باشد.
  • اگر نیاز به‌اضافه کردن متدهای جدید به یک اینترفیس موجود دارید بدون اینکه پیاده‌سازی‌های فعلی را بِشکنید، استفاده از متدهای پیش‌فرض (default methods) را در نظر بگیرید که پیاده‌سازی‌های پیش‌فرض را برای متدهای جدید ارائه می‌دهند.

اصل وارونگی وابستگی (DIP)

اصل وارونگی وابستگی Dependency Inversion Principle (DIP) بیان می‌کند که ماژول‌های سطح بالا (یا کلاس‌ها) نباید به ماژول‌های سطح پایین وابسته باشند؛ بلکه هر دو باید به انتزاع رابط‌ها یا کلاسهای انتزاعی (interfaces or abstract classes) وابسته باشند. به عبارت ساده‌تر، این اصل استفاده از رابط‌های انتزاعی را برای جداکردن کامپوننت‌های سطح بالا از جزئیات سطح پایین تشویق می‌کند.

نمونه‌ای از نقض اصل وارونگی وابستگی (DIP)

برای درک بهتر اصل وارونگی وابستگی (DIP) بیایید نگاهی به یک نمونه کد جاوا بیندازیم که به طور واضح این اصل را نقض می‌کند:

 DIP - bad
DIP - bad

در این مثال، کلاس Switch مستقیماً به کلاس LightBulb وابسته است که یک جزئیات سطح پایین است. این نقضی بر اصل وارونگی وابستگی (DIP) است؛ زیرا ماژول‌های سطح بالا مانند Switch نباید به جزئیات سطح پایین وابسته باشند.

رفع نقض اصل وارونگی وابستگی (DIP)

ما اصل وارونگی وابستگی (DIP) را اعمال خواهیم کرد و خواهیم دید که چگونه می‌توانیم مشکل ذکر شده در بالا را حل کنیم:

DIP - good
DIP - good

در این راه‌حل بازنگری شده، کلاس Switch به رابط Switchable وابسته است که یک انتزاعی است. کلاس LightBulb رابط Switchable را پیاده‌سازی می‌کند. این با اصل وارونگی وابستگی (DIP) مطابقت دارد؛ زیرا ماژول‌های سطح بالا اکنون به انتزاع‌ها (abstractions) به‌جای جزئیات سطح پایین وابسته هستند.

نمایش بصری وارونگی وابستگی، نشان می‌دهد که چگونه ماژول‌های سطح بالا (Switch) به انتزاع‌ها (Switchable) به‌جای پیاده‌سازی‌های واقعی (LightBulb) وابسته هستند که در زیر نشان‌داده‌شده است:

DIP
DIP

خطاها و اشتباهات رایج

همان‌طور که اصل وارونگی وابستگی (DIP) را یاد می‌گیریم، بیایید برخی از خطاها و اشتباهات رایجی را که توسعه‌دهندگان ممکن است با آنها مواجه شوند بررسی کنیم:

  • یک اشتباه رایج وابستگی مستقیم ماژول‌های سطح بال به جزئیات سطح پایین است که اصل وارونگی وابستگی (DIP) را نقض می‌کند.
  • ایجاد انتزاع‌های ضعیف یا به طور ضعیف تعریف شده می‌تواند رعایت مؤثر اصل وارونگی وابستگی (DIP) را به چالش بکشد.

برای پیاده‌سازی مؤثر اصل وارونگی وابستگی (DIP) نکات زیر را در نظر بگیرید:

  • برای نمایش وابستگی‌ها، رابط‌های انتزاعی یا کلاس های انتزاعی معرفی کنید تا به ماژول‌های سطح بالا اجازه دهید به این انتزاع‌ها وابسته باشند.
  • از تزریق وابستگی برای تزریق پیاده‌سازی‌های واقعی به ماژول‌های سطح بال از طریق انتزاع‌هایِ آنها استفاده کنید. این کار باعث پیوستگی ضعیف می‌شود.
  • ترکیب اصل وارونگی وابستگی (DIP) با سایر اصول SOLID می‌تواند منجر به کد تمیزتر و قابل نگهدارتر شود.
  • استفاده از چارچوب‌های تزریق وابستگی مانند Spring یا Guice را برای مدیریت خودکار وابستگی‌ها در نظر بگیرید.

سخن پایانی

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


اصول solidنرم افزارسیستم دیزاینالگوریتممعماری نرم افزار
شاید از این پست‌ها خوشتان بیاید