در دنیای مهندسی نرمافزار، هزینه تغییر اجتنابناپذیر است. با افزایش پیچیدگی برنامهها، پایگاههای کد خشک به داراییهای منفی تبدیل شده و منجر به سیستمهای شکنندهای میشوند که توسعه آنها دشوار بوده و مستعد باگ هستند. اینجاست که اصول SOLID وارد عمل میشوند. این پنج اصل طراحی که توسط رابرت سی. مارتین (آنتی باب) ابداع شدهاند، تنها مفاهیم نظری نیستند، بلکه راهنماهای عملی هستند که به توسعهدهندگان امکان میدهند کد شیگراتر، انعطافپذیرتر و قابل نگهداریتری بنویسند.
برای توسعهدهندگان متوسط تا پیشرفته، عبور از سینتکس پایه و درونیسازی این اصول، گامی حیاتی برای تبدیل شدن به یک مهندس ارشد است. بیایید به بررسی هر اصل بپردازیم و ببینیم چگونه به ساخت معماریهای نرمافزاری مستحکم کمک میکنند.
اصل مسئولیت تک (SRP)
یک کلاس باید یک و فقط یک دلیل برای تغییر داشته باشد. این بدان معناست که یک کلاس باید تنها یک شغل یا مسئولیت داشته باشد. وقتی یک کلاس چندین مسئولیت را مدیریت میکند، مانند محاسبه دادهها و ذخیره آنها در پایگاه داده، تغییر در یک الزام میتواند به طور ناخواسته دیگری را خراب کند.
مثال عملی: یک کلاس User را در نظر بگیرید که نه تنها دادههای کاربر را مدیریت میکند، بلکه ایمیل ارسال کرده و دادهها را در پایگاه داده ذخیره میکند. این کار نقض SRP است. رویکرد بهتری ایجاد سرویسهای جداگانه است:
class UserService {
save(user) { ... }
}
class EmailService {
sendEmail(user, message) { ... }
}
class UserController {
constructor(userService, emailService) {
this.userService = userService;
this.emailService = emailService;
}
register(user) {
this.userService.save(user);
this.emailService.sendEmail(user, "Welcome!");
}
}
با جداسازی دغدغهها، میتوانید منطق ایمیل را بدون به خطر انداختن پایداری لایه ذخیرهسازی دادههای کاربر تغییر دهید.
اصل باز/بسته (OCP)
ارکان نرمافزاری باید برای گسترش باز و برای تغییر بسته باشند. این اصل شما را تشویق میکند تا ماژولها را طوری طراحی کنید که عملکرد جدید از طریق کد جدید اضافه شود، نه با تغییر کد موجود و آزمایششده.
این موضوع اغلب از طریق استفاده از رابطها یا کلاسهای انتزاعی محقق میشود. برای مثال، اگر یک رابط PaymentProcessor داشته باشید، میتوانید یک CryptoPaymentProcessor جدید را بدون تغییر در منطق موجود CreditCardProcessor اضافه کنید. این کار خطر باگهای بازگشتی در سیستمهای اصلی را کاهش میدهد.
اصل جایگزینی لیسکوف (LSP)
اشیاء یک کلاس والد باید با اشیاء زیرکلاسهای آن قابل جایگزینی باشند بدون اینکه برنامه خراب شود. به بیان ساده، یک زیرکلاس باید بتواند جایگزین کلاس والد خود شود بدون اینکه ویژگیهای مطلوب برنامه را تغییر دهد.
یک نقض رایج زمانی رخ میدهد که یک زیرکلاس روشی را بازنویسی میکند اما قرارداد والد را رعایت نمیکند، شاید با پرتاب استثناهای غیرمنتظره یا بازگرداندن انواع داده ناسازگار. پایبندی سختگیرانه به LSP تضمین میکند که چندریختی (Polymorphism) شما ایمن و قابل پیشبینی باشد.
اصل جداسازی رابط (ISP)
رابطهای ظریف و جزئینگر که مخصوص مشتری هستند ایجاد کنید. مشتریان نباید مجبور شوند به روشهایی وابسته باشند که از آنها استفاده نمیکنند. به جای یک رابط بزرگ و «خدایی»، رابطهای کوچک و خاصی متناسب با نیازهای مشتریان مختلف ایجاد کنید.
برای مثال، یک رابط Worker نباید یک کلاس Robot را مجبور به پیادهسازی روش eat() کند. با جداسازی رابطها به Workable و Eatable، طراحیهای منسجمتر و قابلدرکتری ایجاد میکنید.
اصل وارونگی وابستگی (DIP)
ماژولهای سطح بالا نباید به ماژولهای سطح پایین وابسته باشند. هر دو باید به انتزاعات وابسته باشند. انتزاعات نباید به جزئیات وابسته باشند؛ جزئیات باید به انتزاعات وابسته باشند.
این اصل سنگ بنای تزریق وابستگی است. با تزریق وابستگیها (مانند پایگاههای داده، APIها یا سرویسها) از طریق رابطها به جای کلاسهای عینی، منطق کسبوکار سطح بالای خود را از جزئیات پیادهسازی سطح پایین جدا میکنید. این کار تست واحد را به طور قابل توجهی آسانتر میکند، زیرا میتوانید وابستگیها را با کمترین تلاش شبیهسازی (Mock) کنید.
نتیجهگیری
پایبندی به اصول SOLID درباره دنبال کردن خشک و سختگیرانه قوانین نیست، بلکه درباره درک مبادلههای موجود در طراحی نرمافزار است. اگرچه ممکن است نیاز به تلاش اولیه بیشتری برای طراحی صحیح رابطها و کلاسها باشد، اما مزایای بلندمدت آن از نظر قابلیت نگهداری، قابلیت آزمایش و مقیاسپذیری بسیار عظیم است. با اعمال یک اصل در هر بار شروع کنید، کد موجود را بازسازی مجدد (Refactor) کنید و به تدریج ذهنیتی را بسازید که بر معماری تمیز و انعطافپذیر متمرکز است.