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

1. اصل مسئولیت واحد (SRP)
- تعریف: یک کلاس باید فقط یک دلیل برای تغییر داشته باشد، یعنی فقط یک شغل یا مسئولیت داشته باشد.
- مثال: به جای اینکه یک کلاس کاربر هم داده های کاربر و هم احراز هویت کاربر را مدیریت کند، این مسئولیت ها را به دو کلاس تقسیم می کنید: User for data و UserAuthentication برای احراز هویت.
توضیح تفصیلی: این اصل بیان می کند که یک کلاس فقط باید یک دلیل برای تغییر داشته باشد، یعنی فقط یک شغل یا مسئولیت داشته باشد. این باعث میشود کلاس قویتر و نگهداری آن آسانتر شود، زیرا تغییرات در الزامات که بر آن مسئولیت تأثیر میگذارد، تنها مستلزم تغییر در آن کلاس خاص است.
مثال:
public class Invoice { public void CalculateTotal() { /* ... */ } public void PrintInvoice() { /* ... */ } // Violates SRP } public class Invoice { public void CalculateTotal() { /* ... */ } } public class InvoicePrinter { public void PrintInvoice(Invoice invoice) { /* ... */ } // Adheres to SRP }
در مثال اول، کلاس Invoice هم محاسبه و هم چاپ را انجام می دهد و SRP را نقض می کند. در مثال دوم، مسئولیت ها به دو کلاس Invoice و InvoicePrinter تقسیم می شوند.
2. اصل باز/بسته (OCP)
- تعریف: موجودیت های نرم افزار (کلاس ها، ماژول ها، توابع و غیره) باید برای توسعه باز باشند اما برای اصلاح بسته باشند.
- مثال: اگر یک کلاس Shape دارید، باید بتوانید اشکال جدید (مانند دایره یا مربع) را با گسترش کلاس Shape بدون تغییر کد موجود آن اضافه کنید.
توضیح تفصیلی: این اصل بیان میکند که موجودیتهای نرمافزار باید برای توسعه باز باشند اما برای اصلاح بسته باشند. این امر طراحی ماژولها را به گونهای تشویق میکند که قابلیتهای جدید بدون تغییر کد موجود اضافه شود و خطر ایجاد اشکال در عملکرد موجود کاهش یابد.
مثال:
public abstract class Shape { public abstract double Area(); } public class Circle : Shape { public double Radius { get; set; } public override double Area() => Math.PI * Radius * Radius; } public class Rectangle : Shape { public double Width { get; set; } public double Height { get; set; } public override double Area() => Width * Height; }
افزودن یک شکل جدید مانند مثلث فقط نیاز به گسترش کلاس Shape بدون تغییر کد موجود دارد.
3. اصل جایگزینی لیسکوف (LSP)
- تعریف: اشیاء یک سوپرکلاس باید با اشیاء یک زیر کلاس بدون تأثیر بر صحت برنامه قابل تعویض باشند.
- مثال: اگر Bird یک سوپرکلاس و پنگوئن یک زیر کلاس است، پنگوئن باید بتواند بدون ایجاد مشکلی در برنامه جایگزین Bird شود، حتی اگر پنگوئنها نمیتوانند پرواز کنند (این ممکن است به این معنی باشد که رفتارهای پرواز از کلاس پرنده خارج شدهاند. به طور دقیق تر).
توضیح تفصیلی: این اصل بیان می کند که اشیاء یک سوپرکلاس باید با اشیاء یک زیر کلاس بدون تأثیر بر صحت برنامه قابل تعویض باشند. این تضمین میکند که میتوان از زیر کلاسها به جای سوپرکلاس خود بدون ایجاد خطا استفاده کرد.
مثال:
public class Bird { public virtual void Fly() { /* Implementation */ } } public class Sparrow : Bird { public override void Fly() { /* Implementation */ } } public class Ostrich : Bird { // Ostrich can't fly, violating LSP if Fly method is called }
در اینجا کلاس Ostrich LSP را نقض می کند زیرا نمی تواند پرواز کند. برای رفع این مشکل، می توانید یک رابط جداگانه IFlyable برای پرندگان در حال پرواز ایجاد کنید.
public interface IFlyable { void Fly(); } public class Sparrow : Bird, IFlyable { public void Fly() { /* Implementation */ } } public class Ostrich : Bird { // Does not implement IFlyable }
4. اصل جداسازی رابط (ISP)
- تعریف: کلاینت ها نباید مجبور شوند به واسط هایی که استفاده نمی کنند وابسته باشند. در عوض، واسط های خاص را برای مشتریان مختلف ایجاد کنید.
- مثال: به جای یک رابط Animal منفرد با روشهایی مانند fly، swim و run، چندین رابط مانند Flyable، Swimmable و Runnable ایجاد کنید تا کلاسها بتوانند تنها آنچه را که نیاز دارند پیادهسازی کنند.
توضیح تفصیلی: این اصل بیان می کند که هیچ مشتری نباید مجبور شود به روش هایی که استفاده نمی کند وابسته باشد. به جای یک رابط بزرگ، چندین رابط کوچکتر و خاص مشتری ایجاد کنید.
مثال:
public interface IAnimal { void Fly(); void Swim(); void Walk(); } public class Duck : IAnimal { public void Fly() { /* Implementation */ } public void Swim() { /* Implementation */ } public void Walk() { /* Implementation */ } } public class Fish : IAnimal { public void Fly() { throw new NotImplementedException(); } // Violates ISP public void Swim() { /* Implementation */ } public void Walk() { throw new NotImplementedException(); } } // Better way public interface IFlyable { void Fly(); } public interface ISwimmable { void Swim(); } public interface IWalkable { void Walk(); } public class Duck : IFlyable, ISwimmable, IWalkable { public void Fly() { /* Implementation */ } public void Swim() { /* Implementation */ } public void Walk() { /* Implementation */ } } public class Fish : ISwimmable { public void Swim() { /* Implementation */ } }
5. اصل وارونگی وابستگی (DIP)
- تعریف: ماژول های سطح بالا نباید به ماژول های سطح پایین، بلکه به انتزاعات وابسته باشند. ماژول های سطح بالا و سطح پایین باید به انتزاعات بستگی داشته باشند.
- مثال: به جای یک کلاس Logger که مستقیماً به پیاده سازی FileLogger بستگی دارد، یک رابط ILogger ایجاد کنید که FileLogger پیاده سازی می کند. کلاس Logger سپس به ILogger بستگی دارد و باعث میشود که پیادهسازیهای لاگ را تغییر دهید.
توضیح مفصل: این اصل بیان می کند که ماژول های سطح بالا نباید به ماژول های سطح پایین، بلکه به انتزاع ها بستگی داشته باشند. هر دو باید به انتزاعات بستگی داشته باشند و وابستگی به اجرای بتن را کاهش دهند.
مثال:
public class FileLogger { public void Log(string message) { /* Implementation */ } } public class UserService { private FileLogger _logger = new FileLogger(); public void SaveUser(User user) { // Some save logic _logger.Log("User saved."); } } // Better way using DIP public interface ILogger { void Log(string message); } public class FileLogger : ILogger { public void Log(string message) { /* Implementation */ } } public class UserService { private readonly ILogger _logger; public UserService(ILogger logger) { _logger = logger; } public void SaveUser(User user) { // Some save logic _logger.Log("User saved."); } }
در مثال بهتر، UserService به جای FileLogger به ILogger وابسته است، که امکان جایگزینی و آزمایش آسان تر را فراهم می کند.
همه را کنار هم گذاشتن
هنگامی که شما به اصول SOLID پایبند هستید، یک پایگاه کد ایجاد می کنید که:
- ماژولارتر: درک و مدیریت آسان تر.
- انعطاف پذیر: با ویژگی های جدید گسترش آسان تر است.
- قابل نگهداری: اصلاح و اشکال زدایی آسان تر است.
این اصول برای هر برنامه نویس ارشد سی شارپ اساسی است، زیرا بهترین شیوه ها را در طراحی و توسعه نرم افزار ترویج می کنند. درک و به کارگیری اصول SOLID می تواند به طور قابل توجهی کیفیت کد شما را بهبود بخشد و آن را قابل نگهداری تر، مقیاس پذیرتر و درک آسان تر کند.