
اصول SOLID در برنامهنویسی مجموعهای از توصیهها و راهنماییهاست که به ما کمک میکند تا کدهای تمیزتر، منعطفتر و قابل نگهداری بنویسیم. کدهایی که نه تنها به درستی کار میکنند، بلکه در آینده نیز به راحتی قابل توسعه و گسترش هستند. در این آموزش هر 5 اصل SOLID را به زبان ساده و با مثالهای قابل لمس مطرح میکنم.
قطعاً تا به حال کدهای کوچک و متوسطی نوشتهاید که با یک تغییر کوچک، نیازمند تغییرهای زیادی در بخشهای مختلف همان کد شده است. پس چطوری برخی از پروژههای بزرگ نرمافزاری به راحتی توسعه پیدا میکنند؟ فرقی نمیکند که یک برنامهنویس تازهکار باشید یا در سطح متوسط، این آموزش به شما دیدگاهی تازه و کاربردی برای نوشتن کدهای بهتر میدهد.
اصول SOLID چیست؟
اصول SOLID مجموعهای از ۵ توصیه برنامهنویسی است. هدف این اصول کمک به برنامهنویسها برای ایجاد کدهایی با کیفیت بالاتر، قابل نگهداریتر و قابل گسترشتر است. این اصول به ما کمک میکند تا کدی بنویسیم که در برابر تغییرات مقاوم باشند و توسعه آنها در آینده با سادگی بیشتر و پیچیدگی کمتری انجام شود.
این اصول به زبان برنامهنویسی خاصی وابسته نیستند و در هر زبانی که از مفاهیم شیءگرایی (Object-Oriented) پشتیبانی کند، قابل پیادهسازی هستند. بنابراین در هر پروژهای میتوانیم از اصول SOLID استفاده کنیم؛ از جاوا و سی شارپ گرفته تا PHP و پایتون. این اصول شبیه به یک رویکرد طراحی و مجموعهای از توصیهها هستند.
پنج اصل SOLID در برنامهنویسی
این ۵ اصل بهطور خلاصه عبارتاند از:
- اصل مسئولیت واحد (Single Responsibility): هر کلاس فقط باید یک مسئولیت مشخص داشته باشد.
- باز و بسته (Open Closed): کلاس باید برای گسترش باز و برای تغییر بسته باشد.
- جایگزینی لیسکوف (Liskov Substitution): زیر کلاسها باید بتوانند جایگزین کلاس والد شوند.
- تفکیک اینترفیس (Interface Segregation): بهجای یک اینترفیس بزرگ، از چند اینترفیس کوچک استفاده کنید.
- وارونگی وابستگی (Dependency Inversion): بهجای جزئیات، به انتزاعها وابسته باشید.
ممکن است در یک پروژه فقط از چند اصل استفاده کنیم، اما در پروژههای بزرگ و پیچیده که نیاز به نگهداری طولانیمدت دارند، رعایت این اصول به شدت توصیه می شود. حتی گاهی اوقات جزء الزامات پروژه است.
در ادامه این آموزش، هر اصل را بهطور مختصر معرفی کرده و توضیح میدهم. برای برخی از این اصول مثالهای کد و برای برخی فقط مثالهای توضیحی میدهم. هر کدام از اصول SOLID که در این آموزش مطرح میشوند، در آموزشی جداگانه بهطور جامع و کامل، همراه با مثالهای کد و نتایج آنها آموزش داده شدهاند. در صورتی تمایل میتوانید وارد جلسات آموزشی این مینی دوره کوتاه اما کاربردی شوید.
سه نکته:
- رعایت تمام این اصول در هر پروژهای اجباری نیست. بلکه بهتر است به آنها بهعنوان توصیههایی برای بهتر شدن کدها نگاه کنیم.
- کدهای این آموزش به زبان JAVA نوشته شدهاند. اصول SOLID به زبان برنامه نویسی وابسته نیست. شما میتونید از این اصول در زبانهای برنامهنویسی شیءگرای دیگر نیز استفاده کنید. همانطور که در آموزشهای جداگانه هر اصل، از زبانهای پایتون و PHP برای مثالها و کدها استفاده شده است.
- کدها بهصورت انتزاعی نوشته شدهاند. به این معنی که از نوشتن برخی از بخشهای کدها با هدف کاهش پیچیدگی، صرفاً با نوشتن یک comment عبور کردهام.
اصل اول: اصل Single Responsibility (مسئولیت واحد)
اصل مسئولیت واحد یا Single Responsibility (به اختصار SRP) میگوید که «یک کلاس باید تنها یک مسئولیت داشته باشد و تنها برای انجام یک کار خاص طراحی شود». بهعبارت دیگر، هر کلاس یا مااژول باید بهگونهای نوشته شود که تنها یک هدف اصلی را دنبال کند و یک کار مشخص انجام دهد.
این اصل شاید در نگاه اول ساده به نظر برسد، اما در عمل رعایت آن از اهمیت بالایی برخوردار است.
تصور کنید که یک کلاس وظیفه انجام چندین کار مختلف را برعهده دارد؛ مثلاً هم باید اطلاعات کاربر را پردازش کند و هم آنها را ذخیره کند. در این حالت، اعمال تغییر در کدهای بخش پردازش اطلاعات، ممکن است روی کدهای بخش ذخیرهسازی هم تأثیر بگذارد. در حالی که اصل مسئولیت واحد میگوید بهتر است دو کلاس داشته باشید که هر کدام مسئولیت جداگانهای (پردازش یا ذخیره) داشته باشد.
با رعایت این اصل، باید کلاسهایی بنویسیم که هر کدام وظایف مشخص داشته باشند. سپس برای استفاده از وظایف دیگر کلاسها، کافی است یک شیء از کلاس موردنظر در کلاس فعلی ایجاد کنیم و متدهای مربوطه را صدا بزنیم.
مثال کد اصل SRP در اصول SOLID
برای مثال، اگر یک کلاس برای مدیریت موجودیت کاربر (User) داشته باشیم و بخواهیم در آن کارهای مربوط به ذخیرهسازی دادههای کاربر در دیتابیس را انجام دهیم، میتوانیم یک کلاس دیگری صرفاً برای کار با جدول User ایجاد کنیم.
در قطعه کد زیر، کلاس UserDB وظیفه کار با دیتابیس (فراخوانی یا ذخیره و تغییر) کاربر را بر عهده دارد. متد get() مقدار ID کاربر و فیلد مورد جستجو را گرفته و مقدار مربوط به آن را از دیتابیس فراخوانی میکند.
در متد get_email() برای فراخوانی دادهها از دیتابیس از متد get() استفاده کردهایم و به نحوه اتصال یا نام جدول و سایر جزئیات کار با دیتابیس کاری نداریم.
class User { private int uesr_id; // سازنده و سایر متدها public function get_email() { user_db = new UserDB(); user_db.get(this.uesr_id, "email"); }}class UserDB { // متغیرهای نگهداری ارتباط با دیتابیس و جدول // متدهای سازنده public function get(Int user_id, String field){ // اتصال به دیتابیس و گرفتن مقدار فیلد مربوطه retu result; }}در این مثال، کلاسها بهطور جداگانه وظایف خود را انجام میدهند. همچنین در اعمال تغییرات در یک کلاس، کدهای دیگرمان تحت تأثیر تغییرات نخواهند بود. برای استفاده از کدهای بالا، مانند زیر عمل میکنیم:
user = new User(16);user.getEmail();با رعایت اصل مسئولیت واحد، کدهای سادهتر و با پیچیدگی کمتری خواهیم داشت. همچنین با تقسیم مسئولیتها به بخشهای جداگانه، میتوان هر بخش (هر کلاس) را تقریباً بهطور جداگانه تغییر یا توسعه داد و حتی آن را تست کرد.
برای یادگیری کامل و دقیقتر این اصل همراه با مثالهای بیشتر و کدهای واقعی، میتوانید به آموزش جامع آن مراجعه کنید.

اصل دوم: اصل Open Closed (باز و بسته)
اصل باز و بسته (به اختصار OCP) میگوید که «یک کلاس باید برای گسترش و توسعه باز باشد و برای تغییر بسته». یعنی وقتی میخواهیم قابلیت جدیدی به یک کلاس اضافه کنیم، نباید کدهای موجود در آن را تغییر دهیم. بلکه باید با اضافه کردن کدهای جدید رفتار کلاس را گسترش دهیم.
منظور از اضافه کردن کدهای جدید، استفاده از مفاهیم ارثبری، پیادهسازی اینترفیسها یا تزریق وابستگی است.
شاید درک این مفهوم در ابتدا کمی دشوار باشد، اما با یک مثال واقعی کاملاً واضح میشود.
فرض کنید که در حال پیادهسازی بخش پرداخت در یک سیستم فروشگاهی هستیم. در ابتدا ما دو درگاه Mellat و Saman داریم. واضح است که کدهای و توابعی که برای اتصال و استفاده از API بانک استفاده میشود، برای هر بانک متفاوت است.
در قدم اول شاید به ذهنمان برسد که در متد پرداخت، درگاه انتخابشده را بررسی کنیم و با یک ساختار شرطی، کدهایی که لازم است اجرا شوند را بنویسیم. یعنی یک کلاس PaymentProcessor داریم که عملیات پرداخت را مدیریت میکند. در متد پرداخت آن، چیزی مثل قطعه کد زیر را داریم:
class PaymentProcessor { public function do_payment(String gateway, Double amount){ if(gateway == "Mellat") { // کدهای اتصال به ملت } else if (gateway == "Saman") { // کدهای اتصال به سامان } }}مشکل این روش این است که هر بار بخواهیم درگاه جدیدی به سایت اضافه کنیم، مجبوریم همین کد را تغییر دهیم. این کار توصیه نمیشود چون ممکن است به کدهای قبلی آسیب بزنیم.
اعمال اصل OCR از اصول SOLID
برای پیادهسازی صحیح این اصل، بهجای تغییر کد موجود، برای هر درگاه پرداخت یک کلاس مجزا ایجاد میکنیم که همگی از یک اینترفیس مشترک پیروی میکنند. برای مثال:
- اینترفیس IPaymentGateway که متد
pay()را تعیین میکند. - کلاس MellatGateway که IPaymentGateway را پیادهسازی کرده و منطق و کدهای پرداخت ملت را در بر دارد.
- کلاس SamanGateway که IPaymentGateway را پیادهسازی کرده و منطق و کدهای پرداخت سامان را در بر دارد.
حالا هر زمان که بخواهیم درگاه جدیدی اضافه کنیم، کافی است یک کلاس جدید با کدهای مربوط به آن درگاه بسازیم و از اینترفیس IPaymentGateway پیروی کنیم. اینگونه نیازی به دستکاری کدهای قبلی نیست.
استفاده از کدهای جدیدمان که از اصل Open Closed از اصول SOLID در برنامه نویسی پیروی میکند، چیزی شبیه به زیر خواهد شد:
class PaymentProcessor { public function do_payment(IPaymentGateway gateway, double amount){ gateway.pay(amount); }}برای آشنایی بیشتر و یادگیری کامل این اصل، به همراه نکات و مثالهای کامل، پیشنهاد میکنم به آموزش جامع آن مراجعه کنید:

اصل سوم: اصل Liskov Substitution
اصل جایگزینی لیسکوف (LSP) میگوید که «زیرکلاسها باید بتوانند جایگزین کلاس والد خود شوند، بدون اینکه عملکرد برنامه را دچار مشکل کنند». به زبان ساده، اگر کلاس B از کلاس A ارثبری میکند، باید بتوانیم هر جایی که از A استفاده میکنیم، از B نیز بهجای آن استفاده کنیم، بدون اینکه برنامه از کار بیوفتد یا خروجی غیرمنتظرهای داشته باشد.
این اصل از اصول SOLID در برنامه نویسی تضمین میکند که ارثبری بهصورت صحیح انجام شده است.
فرض کنید یک کلاس پایه به نام Rectangle (مستطیل) داریم که متد calculate_area() را برای محاسبه مساحت دارد. همچنین متدهایی برای تعیین عرض و ارتفاع دارد.
سپس میخواهیم کلاس Square (مربع) را به برنامه اضافه کنیم. از نظر ریاضی، مربع نوعی مستطیل است. بنابراین کلاس Square را از Rectangle به ارث میبریم.
اکنون اگر بخواهیم یک شیء مربع را بهجای مستطیل استفاده کنیم با مشکل روبهرو میشویم. چرا؟
چون در مربع، تغییر عرض باعث تغییر ارتفاع هم میشود اما این رفتار برای مستطیل اشتباه است. این تفاوت رفتاری بین کلاسهای والد و فرزند باعث نقض اصل LSP در اصول SOLID میشود.
راهکار رعایت اصل جایگزینی لیسکوف
برای رعایت این اصل، باید بهجای ارثبری مستقیم، بهدنبال یک رابط مشترک (اینترفیس) باشیم. بهجای اینکه Square از Rectangle ارثبری کند، میتوانیم یک اینترفیس کلیتر بهنام Shape (شکل) ایجاد کنیم که متد محاسبه مساحت را تعریف کرده است. یعنی:
- انترفیس Shape که متد
calculate_area()را تعریف میکند. - کلاس Rectangle که Shape را پیادهسازی کرده و متدهای تنظیم ارتفاع، تنظیم عرض و مساحت را دارد.
- کلاس Square که Shape را پیادهسازی کرده و متدهای تنظیم ضلع و محاسبه مساحت را دارد.
با این ساختار، هر دو کلاس Rectangle و Square بهطور مستقل از یک اینترفیس مشترک پیروی میکنند. هر دو نیز متد calculate_area() را دارند.
اکنون اگر در جایی از کد بخواهیم مساحت شکلهای را حساب کنیم، میتوانیم شیء را از نوع Shape در نظر بگیریم. در این حالت، میتوانیم هر دو کلاس مستطیل و مربع را به آن بدهیم و مطمئن باشیم که به متد محاسبه مساحت بدون تداخل در نتیجه محاسباتی در هر دو کلاس دسترسی داریم.
اینگونه کدها و کلاسهای ما قابل اعتمادتر و قابل گسترشتر خواهند بود.
برای آشنایی بیشتر با این اصل از اصول SOLID در برنامه نویسی به همراه مثالهای واقعی و قطعه کدهای گام به گام، میتوانید آموزش جامع این اصل را ببینید:
اصل چهارم: اصل Interface Segregation
اصل تفکیک اینترفیس (ISP) میگوید که «اینترفیسهای بزرگ و همه کاره نباید کلاسها را مجبور به پیادهسازی متدهایی کنند که نیازی به آنها ندارند». به زبان ساده، بهتر است بهجای یک اینترفیس بزرگ و جامع، چند اینترفیس کوچک و تخصصیتر داشته باشیم. این کار باعث میشود که کلاسها فقط متدهایی را پیادهسازی کنند که واقعاً الزامی است.
برای درک بهتر، اجازه دهید یک مثال از دنیا واقعی بزنم. فرض کنید یک شرکت تولید لوازم خانگی، با هدف اینکه دیگران بتوانند نرمافزارهایی برای کار با لوازم این شرکت توسعه دهند یک رابط برنامهنویسی (API) را در قالب یک اینترفیس منتشر میکند.
این اینترفیس بزرگ (مثلاً به نام IAppliance بهمعنای لوازم خانگی)، تمام متدهای مربوط به وسائل مختلف را در خود جای داده است. مثلاً متدهایی بهنام wash_clothes() برای شستن لباس در لباسشویی، cook_food() برای پختن غذا، refrigerate_food() برای سرد کردن غذا دارد.
میخواهیم یک کلاس برای استفاده از مایکروویو (بهنام Microwave) ایجاد کنیم. این کلاس مجبور است متدهای wash_clothes() و refrigerate_food() را نیز پیادهسازی کند در حالی که هیچ کدام در یک مایکروویو کاربردی نیستند.
این وضعیت باعث میشود متدهایی را ایجاد کنیم که هیچ احتیاجی به آنها نداریم. بنابراین کدها شلوغتر و پر از متدهای خالی یا بی معنی و بی کاربرد میشود. در نهایت نیز انعطافپذیری و خوانایی کدها کاهش پیدا میکند.
رعایت اصل ISP در SOLID
برای رعایت این اصل، باید اینترفیسهای بزرگ و چند کاره را به اینترفیسهای کوچکتر با اهداف خاص و تخصصیتر تقسیم کنیم. مثلاً بهجای IAppliance میتوانیم سه اینترفیس جداگانه داشته باشیم:
- اینترفیس IWashable که متد
wash()را برای لوازمی مثل لباسشویی دارد. - اینترفیس ICookable که متد
cook()را دارد. - اینترفیس IRefrigeratable که متد
refrigerate()را دارد.
واضح است که هر اینترفیس میتواند شامل چندین متد باشد. در این مثالها، برای کاهش پیچیدگی صرفاً نام یک متد را نوشتهام. وگرنه وسیلهای مثل لباسشویی، میتواند شامل چند ده متد باشد.
اکنون کلاس Microwave فقط اینترفیس Icookable را پیادهسازی میکند. به این صورت، هیچ کلاسی مجبور نیست متدهایی را پیادهسازی کند که به آنها نیازی ندارد. این کار باعث میشود کدهای ما تمیزتر، خواناتر، منعطفتر و قابل نگهداریتر شود.
برای آشنایی بیشتر و یادگیری کامل این اصل از اصول SOLID در برنامهنویسی، پیشنهاد میکنم آموزش جامع آن را که شامل چند مثال واقعی از دنیای برنامهنویسی با قطعه کدهای کاربردی است را ببینید:

اصل پنجم: اصل Dependency Inversion
اصل وارونگی وابستگی (DIP) میگوید که «ماژولهای سطح بالا نباید به ماژولهای سطح پایین وابسته باشند؛ بلکه هر دو باید به انتزاعها (Abstraction) وابسته باشند». به زبان ساده، کد شما نباید به جزئیات پیادهسازی وابسته باشد، بلکه باید به مفاهیم کلی (نظیر اینترفیس یا کلاسهای انتزاعی) وابسته شود.
منظور از کلاسهای سطح بالا در این توضیح، کلاسهایی هستند که درگیر پردازش و کنترل دادهها هستند. کلاسهای سطح پایین، مستقیماً با منابعی نظیر دیتابیس، شبکه، فایلها و سرویسهای جانبی در ارتباطند.
یک مثال از دنیای واقعی را در نظر بگیرید. فرض کنید یک کلاس Shop داریم که میخواهیم در آن به مدیر سایت اطلاعرسانی انجام داده و پیامی ارسال کنیم. در قطعه کد زیر، بهطور مستقیم از یک کلاس سطح پایین (EmailSender برای ارسال ایمیل) استفاده میکند که از نظر این اصل از اصول SOLID نامناسب است.
class EmailSender { public void send_email(String to, String message){ // کدهای ارسال ایمیل }}class Shop { // سایر کدها، ویژگیها و متدها public void sned_notify(String message){ EmailSender email_sender = new EmailSender(); email_sender.send("[email protected]", message); }}در قطعه کد بالا، کلاس Shop بهطور مستقیم به جزئیات پیادهسازی EmailSender وابسته است. اگر بخواهیم روش اطلاعرسانی را از ایمیل به پیامک تغییر دهیم، باید کلاس Shop را دستکاری کنیم که از نظر اصل وارونگی وابستگی صحیح نیست.
رعایت اصل DIP از اصول سالید
برای رعایت این اصل، وابستگی مستقیم را برمیداریم و بهجای آن از یک انتزاع (در اینجا، اینترفیس) استفاده میکنیم.
- اینترفیس INotificationSender که متد
send()را تعریف میکند. - کلاس EmailSender که INotificationSender را پیادهسازی کرده و برای ارسال پیامهای ایمیلی کاربرد دارد.
اکنون کلاس Shop را بهجای اینکه به یک کلاس مشخص وابسته کنیم، به اینترفیس INotificationSender وابسته میکنیم. چیزی شبیه به قطعه کد زیر:
interface INotificationSender { void send(String to, String message);}class EmailSender implements INotificationSender { @override public void send(String to, String message) { // کدهای ارسال ایمیل }}class Shop { // سایر کدها، ویژگیها و متدها public void send_notify(INotificationSender sender, String Message){ sender.send("[email protected]", message); }}سپس هر زمان که بخواهیم اطلاعیهای ارسال کنیم، شیء مورد نظر (ایمیل یا چیز دیگر) را به آن تزریق میکنیم.
Shop shoo = new Shop();// پردازشها و سایر کارهاINotificationSender email_sender = new EmailSender();Shop.send_notify(email_sender, "This from SabzDanesh.com!");با این روش، کلاس Shop به جزئیات و کلاسهای سطح پایین وابسته نیست و میتوانیم بهراحتی نوع اطلاعرسانی را تغییر دهیم. فقط کافی است یک کلاس مثلاً SMSSender ایجاد کنیم و در هنگام ارسال پیام، آن را به متد send_notify() بدهیم.
این کار به ماژولار بودن کدهایمان کمک زیادی خواهد کرد.
در آموزش جامع مربوط به این اصل، علاوه بر مثال کاملی درباره ارسال ایمیل و پیامک، مثالهایی از کلاسهای سطح پایین که با دیتابیس در ارتباط هستند نیز زده شده است.
جمعبندی آموزش اصول SOLID در برنامه نویسی
دیدید که اصول SOLID صرفاً قوانین خشک و خالی نیستند، بلکه یک رویکرد و طرز تفکر برای کدنویسی بهتر هستند. با رعایت این اصول، کدی مینویسید که در درازمدت ارزشمند خواهد بود و به توسعهدهنده بعدی (یا خودتان در آینده) اجازه میدهد که به راحتی آن را بفهمید و با کمترین تغییرات و وابستگیها، آن را توسعه دهید.
بهطور خلاصه، استفاده از اصول SOLID در برنامهنویسی مزایای زیر را به همراه دارد:
- کاهش پیچیدگی: هر کلاس یک مسئولیت مشخص دارد و کد شما مرتبط خواهد ماند.
- انعطافپذیری بیشتر: با اضافه کردن قابلیتهای جدید، کمتر نیازمند تغییر کدهای قبلی خواهیم بود.
- قابلیت نگهداری آسان: وقتی کدها به بخشهای کوچک و مستقل تقسیم میشوند، اشکالزدایی و رفع باگها بسیار سادهتر میشود.
- کدنویسی قابل اعتماد: با رعایت این اصول، احتمال بروز خطاهای غیرمنتظره در آینده کمتر میشود.
به یاد داشته باشید که رعایت این اصول، بهخصوص در پروژههای بزرگ و تیمی، یک سرمایهگذاری برای آینده است. در جدول زیر، 5 اصل SOLID را خیلی سریع مرور میکنم.
| اصل | معادل فارسی | خلاصه |
|---|---|---|
| Single Responsibility | مسئولیت واحد | هر کلاس فقط یک کار انجام دهد |
| Open Closed | باز و بسته | برای گسترش باز، برای تغییر بسته |
| Liskov Substitution | جایگزینی لیسکوف | کلاس فرزند بتواند جای کلاس والد را بگیرد |
| Interface Segregation | تفکیک اینترفیس | اینترفیسهای تخصصی بهتر از اینترفیس همه کاره است |
| Dependency Inversion | وارونگی وابستگی | به انتزاعها وابسته باشید و نه به جزئیات |
امیدوارم این آموزش و جلسات جانبی آن به شما کمک کرده باشد تا درک بهتری از اصول SOLID در برنامه نویسی پیدا کنید. جالبه بدانید که خالق یا ارائهکننده این اصول، Robert C. Martin یا همان عمو باب معروف است.
اگر سؤال یا نظری دارید، حتماً در بخش دیدگاهها مطرح کنید. همچنین خوشحال میشوم اگر این آموزش را به دوستان برنامهنویس یا در حال یادگیری برنامهنویسی خود به اشتراک بگذارید.
این آموزش بخشی از یک دوره کوتاه، جامع و قدم به قدم در سبز دانش است: آموزش اصول SOLID در برنامهنویسی
این آموزش برای همیشه رایگانه! میتونید با اشتراکگذاری لینک این صفحه از ما حمایت کنید یا با خرید یه فنجون نوشیدنی بهمون انرژی بدید!
میخوام یه نوشیدنی مهمونتون کنم








