منظور از اصول SOLID، قوانین مربوط به طراحی شیءگرا هستند که رابرت سی مارتین (Robert Cecil Martin) آنها را طراحی کرده است. این اصول در زبانهای برنامهنویسی مختلف اجرا میشوند و به توسعه نرمافزار کمک میکنند. در این مقاله، درباره واژه مخفف SOLID صحبت میکنیم و اصول پنجگانه آن را توضیح میدهیم. مطالب این مقاله به شما کمک میکنند که به توسعهدهنده بهتری تبدیل شوید.
منظور از SOLID چیست؟

هر کدام از حرفهای این اصطلاح، به یک اصل اشاره میکنند. شما میتوانید این اصول را در بحثهای مربوط به توسعه نرمافزار، کدهای ریفاکتور، توسعه اجایل و نرمافزار انطباقی اجرا کنید. بهعلاوه، اصول پنجگانه SOLID به درک بهتر الگوهای طراحی و معماری نرمافزار هم کمک میکنند.
- S – Single-responsibility Principle
- O – Open-closed Principle
- L – Liskov Substitution Principle
- I – Interface Segregation Principle
- D – Dependency Inversion Principle
کاربرد SOLID چیست؟
اصول SOLID کاربردهای گستردهای در طراحی نرمافزار دارند و به توسعهدهندگان کمک میکنند تا کدهای ساختاریافته، قابل نگهداری و مقیاسپذیر ایجاد کنند. با استفاده از این اصول، میتوانید از تکرار کد جلوگیری کنید، وابستگیهای غیرضروری را کاهش دهید و فرایند تست و رفع خطا را سادهتر کنید. این اصول به توسعهدهندگان اجازه میدهند تا نرمافزارهایی را طراحی کنند که در عین حفظ پایداری کلی سیستم، به راحتی با تغییرات نیازمندیها یا افزودن ویژگیهای جدید تطبیق پیدا میکنند. در ادامه هر کدام از این اصول و کاربردهای آنها را با استفاده از مثالهای مختلف و تکه کد بررسی میکنیم.
آشنایی با اصول SOLID
اصل مسئولیت محدود (Single responsibility Principle یا SRP)
طبق اصل مسئولیت محدود، یک کلاس فقط یک دلیل برای تغییر دارد و فقط میتواند یک کار را انجام دهد. با رعایت اصل مسئولیت محدود، فرایند تست، نگهداری و درک کدها آسانتر انجام میشود. بهعلاوه، شما میتوانید از SRP در کلاسها، اجزای نرمافزار و میکروسرویسها استفاده کنید و نرمافزار بهتری طراحی کنید. برای درک بهتر SRP، به مثال زیر توجه کنید.

مثال اصل Single responsibility Principle
فرض کنید میخواهید با استفاده از یک برنامه محیط مجموعهای از اشکال هندسی را حساب کنید. در مرحله اول، باید کلاس اشکال هندسی را بسازید و پارامترهای لازم را تنظیم کنید. با استفاده از روش سازنده یا Construct، پارامترها را مشخص کنید. برای محاسبه محیط مربع به طول یک ضلع و برای محاسبه محیط دایره به قطر احتیاج دارید.
در مرحله بعد، کلاس AreaCalculator را بسازید. اشکال مختلف، آرایههای تابع سازنده هستند. در این مرحله، با اجرای متد SUM مجموع را حساب کنید و نتیجه را در قالب HTML یا CSS چاپ کنید. این مثال نشان میدهد که هر کلاس فقط یک کار را انجام میدهد. اگر خروجی را به شکل JSON بخواهید، باید از کلاس SumCalculatorOutputter استفاده کنید، چون کلاس AreaCalculator فقط یک منطق را پردازش میکند.
در کد زیر، هر کلاس فقط یک وظیفه دارد. کلاسهای مربوط به اشکال، مساحت خودشان را محاسبه میکنند و کلاس AreaCalculator فقط وظیفه جمع کردن مساحتها را بر عهده دارد. تقسیم وظایف در این کد باعث میشود تغییرات فقط در یک بخش خاص از کد اعمال شوند، که این موضوع فرایند نگهداری و تست را آسانتر میکند.
class AreaCalculator:
def __init__(self, shapes):
self.shapes = shapes
def calculate_total_area(self):
return sum([shape.area() for shape in self.shapes])
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Circle:
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * self.radius ** 2
اصل باز-بسته (Open-Closed Principle)
در اصل باز-بسته یا OCP، شما میتوانید هر کلاس را توسعه دهید، ولی امکان اصلاح کلاس وجود ندارد. به بیان دیگر، کلاس برای توسعه باز است، ولی برای اصلاح بسته است. این اصل به نگهداری و ثبات کد کمک میکند. برای پایبندی به اصل Open-Closed Principle، میتوانید از انتزاعهای وراثت استفاده کنید. بهعلاوه، لایههای انتزاعی به شما اجازه میدهند که رفتار کلاسها را بدون تغییر مستقیم گسترش دهید.

مثال اصل OCP
برنامه مثال قبل و کلاس AreaCalculator را در نظر بگیرید، این بار روی روش SUM تمرکز میکنیم. فرض کنید کاربر میخواهد با استفاده از متد SUM، محیط شکلهایی مثل مثلث و چندضلعی را هم حساب کند. در این حالت، شما باید فایل کدها را ویرایش کنید و بلوکهای if/else بیشتری را اضافه کنید. این کار اصل OCP را نقص میکند.
برای رعایت اصل باز-بسته، میتوانید منطق مربوط به محاسبه محیط هر شکل را از کلاس AreaCalculator جدا کنید و کدهای محاسبه محیط را به کلاس هر شکل متصل کنید. در این مرحله، میتوانید کلاس دیگری برای شکلهای جدید تنظیم کنید و بدون شکستن کد، آن را به کلاس SUM منتقل کنید.
class Shape:
def area(self):
raise NotImplementedError("Subclass must implement abstract method")
class Triangle(Shape):
def __init__(self, base, height):
self.base = base
self.height = height
def area(self):
return 0.5 * self.base * self.height
اصل جایگزینی لیسکوف (Liskov Substitution Principle)
طبق اصل جایگزینی یا LSP، وقتی اشیای یک کلاس مشتق میشوند، باید بدون تغییر در صحت برنامه، جایگزین کلاس پایه شوند. به بیان دیگر، زمان جایگزینی کلاسهای مشتقشده با کلاسهای پایه، رفتار سیستم نباید تغییر کند. اصل Liskov Substitution با توجه به مفاهیم اصل OCP شکل گرفته است. با توجه به این اصل، برای گسترش کلاس پایه لازم نیست رفتار غیرمنتظرهای را اجرا کنید یا کدها را بشنید؛ بنابراین، اصل جایگزینی به یکپارچگی نرمافزار کمک میکند.

مثال اصل جایگزینی لیسکوف (Liskov Substitution Principle)
در این مثال، مفهوم اصل جایگزینی لیسکوف (LSP) بهخوبی نمایش داده میشود. کلاس اصلی، Bird، متدی به نام fly دارد که هر زیرکلاس آن باید بدون ایجاد خطا یا رفتار غیرمنتظره، این متد را اجرا کند. اگر زیرکلاسها این رفتار را نقض کنند، برنامه خطا میدهد. اما در این کد، هر زیرکلاس رفتار مشخصی برای متد fly تعریف کرده است، که باعث میشود سیستم یکپارچه و بدون نقص کار کند.
class Bird:
def fly(self):
raise NotImplementedError
class Sparrow(Bird):
def fly(self):
return "Sparrow is flying"
class Ostrich(Bird):
def fly(self):
return "Ostrich can't fly"
اصل جداسازی رابط (Interface Segregation Principle)
با توجه به اصل جداسازی رابط یا ISP، وجود چند رابط کوچکتر از چند رابط بزرگتر بهتر است. طبق این اصل، رابطها خاص کلاینت هستند. اگر کلاینتها از متدی استفاده نمیکنند، نباید آن را پیادهسازی کنند. به بیان دیگر، هدف از اصل جداسازی رابط این است، رابطها جدید را متناسب با نیاز مشتری بسازید و از ترکیب ارثبری و جداسازی استفاده کنید. اصل جداسازی رابط به کاربرد رابطها اشاره میکند. اگر رابطی استفاده نمیشود، کلاینت نباید آن را اجرا کند.

مثال اصل جداسازی رابط
فرض کنید یک رابط پرینتر داریم. این رابط از یک رابط بزرگ تشکیل شده است و از متدهای چاپ، اسکن و فکس استفاده میکند. این رابطها بزرگ هستند و اصل ISP را نقص میکنند. بهعلاوه، ممکن است همه چاپگرها از فکس پشتیبانی نکنند. برای اینکه به اصل Interface Segregation Principle پایبند بمانیم، باید کد را تغییر دهیم. با این کار، اینترفیسها به رابطهای کوچکتر تفکیک میشوند.
در نتیجه، برای چاپ، اسکن و فکس از رابطهای جداگانهای استفاده میکنیم و یک کلاس جدید به نام SimplePrinter تعریف میکنیم. این کلاس رابطهای پرینتر و اسکنر را پیادهسازی میکند و از متدهای پرینت و اسکن استفاده میکند. بنابراین، در این حالت به متد فکس احتیاجی ندارید. زمانیکه اینترفیسها جدا شدند، کلاینتها (کلاسها یا ماژولها) فقط از رابطهای خاصی استفاده میکنند و دیگر متد غیرضروری نداریم. با این کار، نگهداری از پایگاه کد راحتتر میشود.
class Printer:
def print(self):
raise NotImplementedError
class Scanner:
def scan(self):
raise NotImplementedError
class SimplePrinter(Printer, Scanner):
def print(self):
return "Printing..."
def scan(self):
return "Scanning..."
اصل وارونگی وابستگی (Dependency Inversion Principle)
اصل DIP برای جداسازی ماژولهای نرمافزار است و به انتزاعات بستگی دارد. طبق اصل وارونگی وابستگی، ماژولهای سطح بالا نباید به ماژولهای سطح پایین وابسته باشند، بلکه هر دو ماژول باید به انتزاعات وابسته باشند. برای رعایت اصل Dependency Inversion Principle میتوانید از الگوهای وارونگی وابستگی استفاده کنید. در نتیجه، کدها انعطافپذیرتر و سازگارتر میشوند.

مثال اصل وارونگی وابستگی
فرض کنید کلاس PasswordReminder را داریم. این کلاس به یک پایگاه داده MySQL وصل است. اتصال MySQLConnection یک ماژول سطح پایین است، در حالی که PasswordReminder ماژول سطح بالاست. طبق اصل DIP، این اسنیپت اصل وابستگی را نقص کرده است، چون کلاس PasswordReminder به MySQLConnection وابسته است.
اگر شما موتور پایگاه داده را تغییر دهید، احتمالا باید کلاس PasswordReminder را ویرایش کنید. این کار باعث نقص اصل open-close Principle میشود. برای برطرف کردن این مشکل، میتوانید کدنویسی را نسبت به یک رابط انجام دهید. با این کار، هر دو ماژولهای سطح بالا و سطح پایین وابسته به انتزاع خواهند بود.
class DatabaseConnection:
def connect(self):
raise NotImplementedError
class MySQLConnection(DatabaseConnection):
def connect(self):
return "Connected to MySQL"
class PasswordReminder:
def __init__(self, db_connection: DatabaseConnection):
self.db_connection = db_connection
def get_connection(self):
return self.db_connection.connect()
سخن آخر
در این مقاله، ۵ اصل SOLID در برنامهنویسی را معرفی کردیم. اگر در مسیر یادگیری برنامهنویسی هستید و میخواهید دولوپر شوید، بهتر است این اصلها را یاد بگیرید و در کدنویسی تمرین کنید. بهعلاوه، شما میتوانید پروژههای SOLID را با بقیه به اشتراک بگذارید، آنها را بررسی کنید و اصلاح کنید. با اجرای اصول پنجگانه SOLID در پروژهها، سیستمهای شما مقیاسپذیرتر میشوند و شما به راحتی میتوانید آنها را بررسی و آزمایش کنید. بهعلاوه، بسیاری از مهندسها در سراسر جهان این اصول را قبول دارند و کدهای باکیفیتی با استفاده از آنها مینویسند. اگر به اصول SOLID پایبند بمانید، مطمئن باشید که کدهای شما به خوبی ساختاریافته خواهند بود و مقاومت خوبی در مقابل تغییرات خواهند داشت.
منابع:
دیدگاهتان را بنویسید