کلمه SOLID مخفف پنج اصل در برنامهنویسی و طراحی شیگرا میباشد.
وقتی با پیروی از این اصول، برنامهها طراحی و پیادهسازی میشود، باعث ایجاد قابلیت توسعه و نگهداری در آنها خواهد شد.
اصول SOLID دستورالعملهایی هستند که با پیروی از آنها، میتوان قابلیت نگهداری، قابلیت فهم و انعطاف پذیر ایجاد کرد. در نتیجه، باعث کاهش پیچیدگیها و وابستگیهای سیستم میشود.
طبق این اصل، یک کلاس باید تنها دارای یک وظیفه واحد باشد و در ادامه فقط باید یک دلیل برای تغییر یا بازنویسی آن وجود داشته باشد.
کلاسی، در یک نرمافزار وظیفه نمایش گزارشهای متنی را دارد. این کلاس، به دو دلیل ممکن است شامل تغییر شود:
اصل Single Responsibility میگوید، که این دو تغییر دارای دو مسئولیت جداگانه میباشند. (دو جنبه از مشکل در واقع دو مسئولیت جداگانه است) بنابراین باید در کلاسهای جداگانه قرار گیرند. قرار دادن دو مسئولیت متفاوت که در زمانهای مختلف تغییر مییابند. در یک واحد نرمافزاری یک طراحی نامناسب به شمار میرود.
برای اعمال این اصل باید، مسئولیتهای هر کلاس را در نظر گرفت. و اگر دارای بیش از یک مسئولیت باشند، این مسئولیتهای اضافی را به بخشهای دیگر منتقل نمود.
طبق این اصل، موجودیتها یک نرمافزار (کلاسها، ماژولها) باید برای توسعه باز باشند ولی در برابر اعمال اصلاحات بسته باشند. این موجودیتها باید به شکلی طراحی شوند که قابل توسعه باشد، بدون اینکه تغییری در کد منبع، ایجاد شود.
باز باشند به این معنی است که، ما باید بتوانیم ویژگیها یا اجزای جدیدی را بدون تغییر در کد اصلی به برنامه اضافه کنیم.
بسته باشند به این معنی است که، ما نباید تغییرات احتمالی را، به شکل قطعی در کد اصلی ایجاد کنیم. زیرا این امر شما را در هنگام تغییر مجبور به بازنویسی کدها میکند.
به عبارت سادهتر، این به معنای ایجاد موجودیتهای نرمافزاری به شکلی است که، رفتار آنها بدون نیاز به ویرایش مجدد کدهای اصلی قابل تغییر باشد.
فرض کنید، کلاسی، محتوایی را در فایلی ذخیره میکند. اما نام آن فایل به صورت داخلی در کدهای آن ثبت شده است. اگر زمانی نام فایل هدف تغییر کند، برای عملکرد صحیح، باید محتوای کدهای داخل کلاس بازنویسی شود، اما اگر نام فایل به عنوان یک پارامتر، از کلاینت قابل دریافت بود، (برای توسعه باز) میتوانستیم رفتار را بدون تغییر کد منبع ویرایش کنیم. (برای اصلاح بسته)
طبق این اصل، بخشهای مشتق شده یا کلاسهای فرزند، باید بتوانند جایگزین بخشهای پایه یا کلاسهای پدر خود شوند.
اصل Liskov Substitution تضمین میکند که هر کلاسی که فرزند یک کلاس پدر باشد، باید بتواند به جای پدر خود بدون هیچ رفتار غیر منتظرهای مورد استفاده قرار گیرد.
در مثال، وقتی شغل اجدادی خانوادهای کشاورزی باشد، پسر خانواده، باید مهارتهای کشاورزی را از پدرش به ارث برده و در صورت نیاز بتواند جایگزین پدرش شود.
اگر پسر بخواهد شغل دیگری را ادامه دهد، و رفتار متفاوت داشته باشد، قطعا نمیتواند جایگزین پدرش شود، حتی با توجه به این که هر دو به یک خانواده و سلسله مراتب تعلق دارند.
یکی از نمونههای دیگر این اصل، کلاس مستطیل است. (ارتفاع و عرض مستطیل میتواند هر مقدار باشد) اما (مربع، مستطیلی با عرض و ارتفاع برابر است)
بنابراین گفته میشود که، میتوانیم ویژگیهای کلاس مستطیل را به کلاس مربع گسترش دهیم. (از کلاس مستطیل، برای کلاس مربع ارثبری کرد)
برای انجام این کار، باید کلاس فرزند را با گسترش کلاس پدر به شکلی باز نویسی کنید تا با تعریف مربعی که چهار ضلع برابر دارد مطابقت داشته باشد.
اما با انجام این کار نمیتوان هر زمان کلاس فرزند (مربع) را با کلاس پدر (مستطیل) جایگزین کرد.
پس با این کار، اصل Liskov Substitution را نقض میشود.
(بنابراین صحیح نیست که برای تعریف کلاس مربع از کلاس مستطیل، ارثبری کرد)
این اصل بیان میکند که، هیچ کلاسی را نباید مجبور به پیاده سازی رابطی (Interface) کرد که، دارای بخشهای بدونه استفاده برای آن باشد.
هدف اصلی، تمرکز بر اجتناب از Interface های بزرگ با مسئولیتهای پراکنده و متفاوت است. همچنین این اصل باعث اولویت دادن به Interface های کوچک با مسئولیتهای خاص و مشخص میگردد.
فرض کنید شما کاملا گیاهخوار هستید، وارد یک رستوران شدید. گارسون به شما، لیستی شامل همه اقلام گیاهی، غیر گیاهی، نوشیدنیها و … را میدهد. شما در انتخاب با مشکل روبرو خواهید شد.
بهتر است ،رستوران برای مشتریان کاملا گیاهخوار، دارای یک منو که فقط شامل اقلام گیاهی هست باشد، نه هر چیزی قابل سفارش.
منوها باید برای انواع مختلف مشتریان متفاوت طراحی شوند. (منو که شامل همه اقلام مشترک برای تمام افراد باشد را میتوان به چند زیر منو تقسیم کرد.
اصل Interface Segregation باعث ایجاد گسستگی بخشها در سیستم میشود، به طریقی که اصلاح، تغییر و انتشار مجدد کدها آسانتر خواهد گشت.
طبق این اصل:
اگر یک ماژول یا کلاس سطح بالا به ماژول یا کلاس سطح پایین وابسته باشد، کد شما جفت شدگی خواهد داشت. و اگر بخواهید در یک کلاس تغییری ایجاد کنید، میتواند باعث ایجاد مشکل در کلاس دیگر شود.
اصل Dependency Inversion به این معنا است که، بجای اینکه ماژولهای سطح پایین Interface های قابل استفاده را در اختیار سطوح بالاتر سیستم قرار دهند، ماژولهای سطح بالا، Interface های مورد پذیرش را تعریف میکنند که، توسط ماژولهای سطح پایین پیادهسازی میشوند.
در مثال باتری کنترل تلویزیون را در نظر بگیرید. کنترل تلویزیون به باتری نیاز دارد، اما نه فقط از یک برند یا مارک خاص. شما میتوانید از هر برند و مارک که میخواهید استفاده کنید. (در نتیجه وابستگی کنترل تلویزیون در انتزاع از یک باتری میباشد نه جزئیات آن)
این امر باعث ایجاد قابلیت استفاده مجدد از بخشهای مختلف و همچنین بهبود توسعه پذیری میشود.