SOLID principles make it easy for a developer to write easily extendable code and avoid common coding errors.
These principles were introduced by Robert C. Martin, and they have become a fundamental part of object-oriented programming.
In the context of .NET development, adhering to SOLID principles can lead to more modular, flexible, and maintainable code. In this article, we’ll delve into each SOLID principle with practical coding examples in C#.
Following are the five SOLID Design Principles:
رعایت اصول SOLID در کدنویسی، توسعه کد را برای توسعهدهندگان آسان تر میکند و از خطاهای رایج کدنویسی جلوگیری میکند.
این اصول توسط رابرت سی. مارتین (عمو باب) معرفی شده اند و یکی از اساسی ترین بخش های برنامه نویسی شی گرا هستند.
در زمینه توسعه دات نت، رعایت اصول SOLID می تواند باعث میشود کدهایی ماژولارتر، انعطاف پذیرتر و با قابلیت نگهداری بالاتر بنویسیم. در این مقاله، با ارائه مثالهای کاربردی، به بررسی هر یک از اصول SOLID میپردازیم.
در ادامه پنج اصل طراحی SOLID آمده است:
1. Single Responsibility Principle (SRP)
The SRP states that a class should have only one reason to change, meaning it should have only one responsibility. This promotes modularization and makes the code easier to understand and maintain.
Key Idea: A class should do only one thing, and it should do it well.
Real-Time Example: Think of a chef who only focuses on cooking, not managing the restaurant or delivering food.
۱. اصل تک مسئولیتی (SRP)
اصل SRP بیان می کند که یک کلاس باید تنها یک دلیل برای تغییر داشته باشد، به این معنی که باید فقط یک مسئولیت داشته باشد. رعایت این اصل، ماژولارسازی را ارتقا میدهد و درک و نگهداری کد را آسانتر میکند.
ایده اصلی: یک کلاس باید فقط یک کار را انجام دهد و آن را به خوبی انجام دهد.
مثال در دنیای واقعی: سرآشپزی را در نظر بگیرید که فقط روی آشپزی تمرکز می کند، نه مدیریت رستوران یا تحویل غذا.
مثال کاربردی در #C:
قبل از اعمالSRP:
public class Report { public void GenerateReport() { } public void SaveToFile() { } }
In this scenario, the Report
class has two responsibilities: generating a report and saving it to a file. This violates the SRP.
در این سناریو، کلاس Report
دو وظیفه دارد: ایجاد گزارش و ذخیره آن در فایل. این SRP را نقض می کند.
بعد از اعمال SRP:
public class Report { public void GenerateReport() { } } public class ReportSaver { public void SaveToFile() { } }
Now, theReport
class is responsible only for generating reports, while theReportSaver
class is responsible for saving reports. Each class has a single responsibility.
Explanation: According to SRP, one class should take one responsibility hence to overcome this problem we should write another class to save the report functionality. If you make any changes to theReport
class will not affect theReportSaver
class.
اکنون کلاس Report
فقط مسئول ایجاد گزارش است در حالی که کلاس ReportSaver
وظیفه ذخیره گزارش ها را بر عهده دارد. هر کلاس یک مسئولیت دارد.
توضیح: طبق SRP، هر کلاس باید فقط یک مسئولیت داشته باشد، بنابراین برای غلبه بر این مشکل باید کلاس دیگری را برای ذخیره گزارش بنویسیم. اگر تغییری در کلاس Report
ایجاد کنیم روی کلاس ReportSaver
تاثیری نخواهد داشت.
2. Open/Closed Principle (OCP)
The Open/Closed Principle suggests that a class should be open for extension but closed for modification. This means you can add new features without altering existing code.
Key Idea: Once a class is written, it should be closed for modifications but open for extensions.
Real-Time Example: Your smartphone — you don’t open it up to add features; you just download apps to extend its capabilities.
۲. اصل باز/بسته (OCP)
اصل Open/Closed پیشنهاد می کند که یک کلاس باید برای توسعه باز باشد اما برای اصلاح بسته شود. این بدان معنی است که شما می توانید ویژگی های جدیدی را بدون تغییر کد موجود اضافه کنید.
ایده اصلی: هنگامی که یک کلاس نوشته می شود، باید برای تغییرات بسته شود اما برای توسعه باز باشد.
مثال در دنیای واقعی: گوشی هوشمند شما — آن را برای افزودن ویژگیها باز نمیکنید. شما فقط برنامه ها را دانلود میکنید تا قابلیت های آن را افزایش دهید.
مثال کاربردی در #C:
قبل از اعمال OCP:
public class Rectangle { public double Width { get; set; } public double Height { get; set; } } public class AreaCalculator { public double CalculateArea(Rectangle rectangle) { return rectangle.Width * rectangle.Height; } }
This design may become problematic when adding new shapes. Modifying the AreaCalculator
for each new shape violates the OCP.
این طراحی ممکن است هنگام اضافه کردن یک شکل جدید مشکل ساز شود. اصلاح AreaCalculator
برای هر شکل جدید، OCP را نقض می کند.
بعد از اعمال OCP:
public interface IShape { double CalculateArea(); } public class Rectangle : IShape { // implementation } public class Circle : IShape { // implementation }
By introducing an interface (IShape
), new shapes likeCircle
can be added without modifying existing code, adhering to the OCP.
Explanation: According to OCP, the class should be open for extension but closed for modification. So, When you introduce a new shape, then just implement it from the interfaceIShape
. SoIShape
is open for extension but closed for further modification.
با معرفی یک Interface به اسم IShape
، ضمن رعایت OCP می توان شکل های جدیدی مانند Circle
بدون تغییر در کد موجود، اضافه کرد.
توضیح: طبق OCP، کلاس باید برای توسعه باز باشد اما برای اصلاح بسته باشد. بنابراین، هنگام معرفی یک شکل جدید، فقط کافی است آن را در قالب IShape
پیاده سازی کنید. بنابراین IShape
برای گسترش باز است اما برای اصلاح بیشتر بسته است.
3. Liskov Substitution Principle (LSP)
The Liskov Substitution Principle states that objects of a superclass should be replaceable with objects of a subclass without affecting the correctness of the program.
Key Idea: You should be able to use any subclass where you use its parent class.
Real-Time Example: You have a remote control that works for all types of TVs, regardless of the brand.
۳. اصل جایگزینی لیسکوف (LSP)
اصل جایگزینی لیسکوف بیان می کند که اشیاء یک سوپرکلاس، باید بدون تأثیر بر صحت برنامه، با اشیاء یک زیرکلاس قابل تعویض باشند.
ایده اصلی: شما هر جا از یک کلاس والد استفاده کرده اید، باید بتوانید از هر زیرکلاس آن نیز استفاده کنید.
مثال در دنیای واقعی: شما یک ریموت کنترل دارید که برای همه انواع تلویزیون، کار می کند؛ بدون در نظر گرفتن برند آن.
مثال کاربردی در #C:
قبل از اعمال LSP:
public class Bird { public virtual void Fly() { /* implementation */ } } public class Penguin : Bird { public override void Fly() { throw new NotImplementedException("Penguins can't fly!"); } }
Here, thePenguin
class violates the LSP by throwing an exception for theFly
method.
در اینجا، کلاس Penguin
با ایجاد یک Exception برای متد Fly
، اصل LSP را نقض می کند.
بعد از اعمال LSP:
public interface IFlyable { void Fly(); } public class Bird : IFlyable { public void Fly() { // implementation specific to Bird } } public class Penguin : IFlyable { public void Fly() { // implementation specific to Penguins throw new NotImplementedException("Penguins can't fly!"); } }
By introducing theIFlyable
interface, bothBird
andPenguin
adhere to the Liskov Substitution Principle.
Explanation: According to LSP, a derived class should not break the base class’s type definition and behavior which means objects of a base class shall be replaceable with objects of its derived classes without breaking the application. This needs the objects of derived classes to behave in the same way as the objects of your base class.
با تعریف اینترفیس IFlyable
، هر دو کلاس Bird
و Penguin
به اصل جایگزینی Liskov پایبند هستند.
(نظر خودم: در واقع اینجا دیگه کلاس Penguin از کلاس Bird مشتق نشده که بخواد جایگزینی اتفاق بیفته! یه جورایی صورت مسئله رو پاک کرده. در کل مثال هایی که آورده خیلی جالب نیست D:)
توضیح: طبق LSP، یک کلاس مشتق شده نباید تعریف و رفتار کلاس پایه را نقض کند، به این معنی که اشیاء یک کلاس پایه باید با اشیاء کلاس های مشتق شده آن (بدون خراب شدن برنامه) قابل تعویض باشند. برای این کار نیاز است که اشیاء کلاس های مشتق شده مانند اشیاء کلاس پایه شما رفتار کنند.
4. Interface Segregation Principle (ISP)
The Interface Segregation Principle states that a class should not be forced to implement interfaces it does not use. This principle encourages the creation of small, client-specific interfaces.
Key Idea: A class should not be forced to implement interfaces it doesn’t use.
Real-Time Example: You sign up for a music streaming service and only choose the genres you like, not all available genres.
۴. اصل جداسازی اینترفیس (ISP)
اصل جداسازی اینترفیس (Interface) بیان می کند که یک کلاس نباید مجبور به پیاده سازی اینترفیس هایی شود که از آنها استفاده نمی کند. این اصل ما را تشویق میکند که اینترفیس هایی کوچک و سفارشی ایجاد کنیم.
ایده اصلی: یک کلاس نباید مجبور به پیاده سازی اینترفیس هایی شود که از آنها استفاده نمی کند.
مثال در دنیای واقعی: شما برای یک سرویس پخش موسیقی ثبت نام می کنید و فقط ژانرهای مورد علاقه خود را انتخاب می کنید، نه همه ژانرهای موجود را.
مثال کاربردی در #C:
قبل از اعمال ISP:
public interface IWorker { void Work(); void Eat(); } public class Manager : IWorker { // implementation } public class Robot : IWorker { // implementation }
TheRobot
class is forced to implement theEat
method, violating ISP.
کلاس Robot
مجبور به پیاده سازی متد Eat
شده است که ISP را نقض می کند.
بعد از اعمال ISP:
public interface IWorkable { void Work(); } public interface IEatable { void Eat(); } public class Manager : IWorkable, IEatable { // implementation } public class Robot : IWorkable { // implementation }
By splitting theIWorker
interface into smaller interfaces (IWorkable
andIEatable
), classes can implement only what they need, adhering to ISP.
Explanation: According to ISP, any client should not be forced to use an interface that is irrelevant to it. In other words, clients should not be forced to depend on methods that they do not use.
با تقسیم اینترفیس IWorker
به اینترفیس های کوچکتر (IWorkable
و IEatable
)، کلاسها میتوانند تنها آنچه را که نیاز دارند، با رعایت ISP پیادهسازی کنند.
توضیح: طبق ISP، هیچ کلاسی نباید مجبور شود اینترفیسی را پیاده سازی کند که به آن مربوط نیست و از آنها استفاده نمی کند.
5. Dependency Inversion Principle (DIP)
The Dependency Inversion Principle suggests that high-level modules should not depend on low-level modules, but both should depend on abstractions. Additionally, abstractions should not depend on details; details should depend on abstractions.
Key Idea: High-level modules should not depend on low-level modules; both should depend on abstractions.
Real-Time Example: Building a LEGO tower — the bricks (high and low-level modules) connect through smaller bricks (abstractions).
۵. اصل وارونگی وابستگی (DIP)
اصل وارونگی وابستگی نشان میدهد که ماژولهای سطح بالا نباید به ماژولهای سطح پایین وابسته باشند، اما هر دو باید به انتزاعها (Abstractions) وابسته باشند. علاوه بر این، انتزاع ها نباید به جزئیات بستگی داشته باشند؛ بلکه جزئیات باید به انتزاعات بستگی داشته باشد.
ایده اصلی: ماژول های سطح بالا نباید به ماژول های سطح پایین وابسته باشند. هر دو باید به انتزاعات وابستگی داشته باشند.
مثال در دنیای واقعی: ساخت برج لگو - آجرها (ماژول های سطح بالا و پایین) از طریق آجرهای کوچکتر (abstractions) به هم متصل می شوند.
مثال کاربردی در #C:
قبل از اعمال DIP:
public class LightBulb { public void TurnOn() { /* implementation */ } public void TurnOff() { /* implementation */ } } public class Switch { private LightBulb bulb; public Switch(LightBulb bulb) { this.bulb = bulb; } public void Toggle() { if (bulb.IsOn) bulb.TurnOff(); else bulb.TurnOn(); } }
TheSwitch
class directly depends on the concreteLightBulb
class, violating DIP.
کلاس Switch
مستقیماً از متدهای کلاس LightBulb
استفاده کرده است و به آن وابستگی دارد؛ که این موضوع DIP را نقض می کند.
بعد از اعمال DIP:
public interface ISwitchable { void TurnOn(); void TurnOff(); } public class LightBulb : ISwitchable { // implementation } public class Switch { private ISwitchable device; public Switch(ISwitchable device) { this.device = device; } public void Toggle() { if (device.IsOn) device.TurnOff(); else device.TurnOn(); } }
By introducing an interface (ISwitchable
), theSwitch
class now depends on an abstraction, adhering to the Dependency Inversion Principle.
Explanation: According to DIP, do not write any tightly coupled code because that is a nightmare to maintain when the application is growing bigger and bigger. If a class depends on another class, then we need to change one class if something changes in that dependent class. We should always try to write loosely coupled classes.
با معرفی اینترفیس ISwitchable
، کلاس Switch
اکنون به یک انتزاع وابسته است که به اصل Dependency Inversion پایبند است.
توضیح: با توجه به DIP، کد Tightly Coupled (یعنی کدی که با تغییر در ساختار یک کلاس مجبور میشوید کلاس های دیگری را نیز تغییر دهید) ننویسید، چرا که نگهداری آن یک کابوس است وقتی که برنامه بزرگتر و بزرگتر می شود. اگر کلاس A به کلاس B وابستگی داشته باشد، در صورت تغییر کلاس B باید کلاس A را نیز تغییر دهیم. ما باید همیشه سعی کنیم کلاس های Loosly Coupled بنویسیم که کمترین وابستگی به یکدیگر را داشته باشند.
Conclusion
Remember, by understanding and applying these SOLID principles, .NET developers can create more robust, flexible, and maintainable software. It’s important to note that these principles work together and complement each other, contributing to the overall design philosophy of object-oriented programming.
نتیجه
به یاد داشته باشید، با درک و به کارگیری اصول SOLID، توسعه دهندگان دات نت می توانند نرم افزاری قوی تر، انعطاف پذیرتر و با قابلیت نگهداری بالاتری ایجاد کنند. توجه به این نکته مهم است که این اصول با هم کار می کنند و مکمل یکدیگر هستند و به فلسفه طراحی کلی برنامه نویسی شی گرا کمک می کنند.
(لینک مطلب اصلی: ذکر شده در اولین کامنت)