اصول S.O.L.I.D در Clean Code (قسمت6)

اصول SOLID چیست؟تنها یک مسئولیت-Single Responsibility Principle (SRP)باز برای توسعه بسته برای تغییر-Open/Closed Principle (OCP)جایگزینی لیسکوف-Liskov Substitution Principle (LSP)تفکیک واسط ها-Interface Segregation Principle (ISP)وارانه سازی وابستگی ها-Dependency Inversion Principle (DIP)خود را تکرار نکنید-Don’t repeat yourself (DRY)اصول SOLID چیست؟کلمه SOLID مخفف که توسط Michael Feathers ساخته بر اساس اول حرف پنج اصل Robert Martin معرفی شده است ، که به معنای پنج اصل اساسی برنامه نویسی و طراحی شی گرا است.S: Single Responsibility Principle (SRP)O: Open/Closed Principle (OCP)L: Liskov Substitution Principle (LSP)I: Interface Segregation Principle (ISP)D: Dependency Inversion Principle (DIP)تنها یک مسئولیت Single Responsibility Principle (SRP)همانطور که در Clean Code بیان شده است ،"هرگز نباید بیش از یک دلیل برای تغییر کلاس وجود داشته باشد". ایجاد یک کلاس با قابلیت های زیادی وسوسه انگیز است ، مانند زمانی که فقط می توانید یک چمدان را در پرواز بگیرید.مسئله این است که کلاس شما از نظر مفهومی منسجم نخواهد بود و دلایل زیادی برای تغییر ایجاد می کند.به حداقل رساندن مقدار زمان لازم برای تغییر یک کلاس مهم است.این مهم است زیرا اگر عملکرد(functionality) بیش از حد در یک کلاس باشد و شما یک تکه از آن را تغییر دهید ،درک این مسئله که سایر ماژولهای وابسته در codebase شما چه تاثیری خواهد داشت دشوار خواهد بود.Bad:class UserSettings { private User User; public UserSettings(User user) { User = user; } public void ChangeSettings(Settings settings) { if (verifyCredentials()) { // ... } } private bool VerifyCredentials() { // ... } }Good:class UserAuth { private User User; public UserAuth(User user) { User = user; } public bool VerifyCredentials() { // ... } } class UserSettings { private User User; private UserAuth Auth; public UserSettings(User user) { User = user; Auth = new UserAuth(user); } public void ChangeSettings(Settings settings) { if (Auth.VerifyCredentials()) { // ... } } }باز برای توسعه بسته برای تغییر Open/Closed Principle (OCP)همانطور که Bertrand Meyer گفته است،"موجودیت های نرم افزاری (کلاس ها ، ماژول ها ، توابع و غیره) باید برای گسترش باز اما برای اصلاح بسته باشند.". معنی این چیست؟این اصل اساساً بیان می کند که شما باید به کاربران اجازه دهید بدون تغییر کد موجود ، ویژگی های جدیدی(functionalities) اضافه کنند.Bad:abstract class AdapterBase { protected string Name; public string GetName() { return Name; } } class AjaxAdapter : AdapterBase { public AjaxAdapter() { Name = &quotajaxAdapter" } } class NodeAdapter : AdapterBase { public NodeAdapter() { Name = &quotnodeAdapter" } } class HttpRequester : AdapterBase { private readonly AdapterBase Adapter; public HttpRequester(AdapterBase adapter) { Adapter = adapter; } public bool Fetch(string url) { var adapterName = Adapter.GetName(); if (adapterName == &quotajaxAdapter&quot) { return MakeAjaxCall(url); } else if (adapterName == &quothttpNodeAdapter&quot) { return MakeHttpCall(url); } } private bool MakeAjaxCall(string url) { // request and return promise } private bool MakeHttpCall(string url) { // request and return promise } }Good:interface IAdapter { bool Request(string url); } class AjaxAdapter : IAdapter { public bool Request(string url) { // request and return promise } } class NodeAdapter : IAdapter { public bool Request(string url) { // request and return promise } } class HttpRequester { private readonly IAdapter Adapter; public HttpRequester(IAdapter adapter) { Adapter = adapter; } public bool Fetch(string url) { return Adapter.Request(url); } }جایگزینی لیسکوف Liskov Substitution Principle (LSP)این یک اصطلاح ترسناک برای یک مفهوم بسیار ساده است.به طور رسمی اینگونه تعریف می شود "اگر S زیر مجموعه ای از T باشد ، ممکن است اشیاء از نوع T با اشیاء از نوع S جایگزین شوند (به عنوان مثال اشیاء از نوع S می توانند اشیاء نوع T را جایگزین کنند) بدون اینکه هیچ یک از خصوصیات مطلوب آن برنامه را تغییر دهند. (از نظر درستی ، انجام کار و غیره). ". حتی این یک تعریف ترسناک است.بهترین توضیح برای این امر، اگر کلاس parent و کلاس child دارید ،پس کلاس پایه و کلاس child بدون گرفتن نتایج نادرست قابل تعویض است.ممکن است هنوز هم گیج کننده باشد ،بنابراین اجازه دهید نگاهی به مثال کلاسیک مربع-مستطیل بیندازیم.از نظر ریاضی ، یک مربع ،مستطیل است اما اگر آن را با استفاده از مدل رابطه "is-a" (هست یک) از طریق وراثت الگوبرداری کنید ، به سرعت دچار مشکل می شوید.Bad:class Rectangle { protected double Width = 0; protected double Height = 0; public Drawable Render(double area) { // ... } public void SetWidth(double width) { Width = width; } public void SetHeight(double height) { Height = height; } public double GetArea() { return Width * Height; } } class Square : Rectangle { public double SetWidth(double width) { Width = Height = width; } public double SetHeight(double height) { Width = Height = height; } } Drawable RenderLargeRectangles(Rectangle rectangles) { foreach (rectangle in rectangles) { rectangle.SetWidth(4); rectangle.SetHeight(5); var area = rectangle.GetArea(); // BAD: Will return 25 for Square. Should be 20. rectangle.Render(area); } } var rectangles = new[] { new Rectangle(), new Rectangle(), new Square() }; RenderLargeRectangles(rectangles);Good:abstract class ShapeBase { protected double Width = 0; protected double Height = 0; abstract public double GetArea(); public Drawable Render(double area) { // ... } } class Rectangle : ShapeBase { public void SetWidth(double width) { Width = width; } public void SetHeight(double height) { Height = height; } public double GetArea() { return Width * Height; } } class Square : ShapeBase { private double Length = 0; public double SetLength(double length) { Length = length; } public double GetArea() { return Math.Pow(Length, 2); } } Drawable RenderLargeRectangles(Rectangle rectangles) { foreach (rectangle in rectangles) { if (rectangle is Square) { rectangle.SetLength(5); } else if (rectangle is Rectangle) { rectangle.SetWidth(4); rectangle.SetHeight(5); } var area = rectangle.GetArea(); rectangle.Render(area); } } var shapes = new[] { new Rectangle(), new Rectangle(), new Square() }; RenderLargeRectangles(shapes);تفکیک واسط ها Interface Segregation Principle (ISP)اصل ISP میگوید که "Client ها نباید مجبور شوند به Interface هایی که از آنها استفاده نمی کنند وابستگی داشته باشند.".یک مثال خوب برای نشان دادن این اصل است کلاسهایی است که به اشیاء تنظیمات بزرگ احتیاج دارند(large settings objects).عدم نیاز به clientها برای تنظیم مقدار زیادی از گزینه ها مفید است ، زیرا بیشتر اوقات به همه تنظیمات نیاز نخواهند داشت. ایجاد اختیاری آنها در جلوگیری از داشتن "رابط چاق"("fat interface") کمک می کند.Bad:public interface IEmployee { void Work(); void Eat(); } public class Human : IEmployee { public void Work() { // ....working } public void Eat() { // ...... eating in lunch break } } public class Robot : IEmployee { public void Work() { //.... working much more } public void Eat() { //.... robot can't eat, but it must implement this method } }Good:Not every worker is an employee, but every employee is an worker.public interface IWorkable { void Work(); } public interface IFeedable { void Eat(); } public interface IEmployee : IFeedable, IWorkable { } public class Human : IEmployee { public void Work() { // ....working } public void Eat() { //.... eating in lunch break } } // robot can only work public class Robot : IWorkable { public void Work() { // ....working } }وارانه سازی وابستگی ها Dependency Inversion Principle (DIP)این اصل دو چیز اساسی را بیان می کند:1-ماژول های سطح بالا (High-level) نباید به ماژول های سطح پایین (low-level) بستگی داشته باشند.هر دو باید به انتزاع(abstractions) بستگی داشته باشند.2-انتزاع (abstractions) نباید به جزئیات بستگی داشته باشد.جزئیات باید به انتزاع بستگی داشته باشد.درک این در ابتدا دشوار است ،اما اگر با NET/.NET Core framework کار کرده اید ، شما شاهد پیاده سازی این اصل در قالب Dependency Injection (DI) هستید.در حالی که مفاهیم آنها یکسان نیستند ،DIP ماژول های سطح بالا را از دانستن جزئیات ماژول های سطح پایین و تنظیم آنها حفظ می کند.می تواند این کار را از طریق DI انجام دهد.فایده بزرگ این امر این است که اتصال بین ماژول ها را کاهش می دهد. کوپلینگ (Coupling - اتصال)یک الگوی توسعه خیلی بد است زیرا باعث می شود تا کد شما سخت refactor شود.Bad:public abstract class EmployeeBase { protected virtual void Work() { // ....working } } public class Human : EmployeeBase { public override void Work() { //.... working much more } } public class Robot : EmployeeBase { public override void Work() { //.... working much, much more } } public class Manager { private readonly Robot _robot; private readonly Human _human; public Manager(Robot robot, Human human) { _robot = robot; _human = human; } public void Manage() { _robot.Work(); _human.Work(); } }Good:public interface IEmployee { void Work(); } public class Human : IEmployee { public void Work() { // ....working } } public class Robot : IEmployee { public void Work() { //.... working much more } } public class Manager { private readonly IEnumerable<IEmployee> _employees; public Manager(IEnumerable<IEmployee> employees) { _employees = employees; } public void Manage() { foreach (var employee in _employees) { _employee.Work(); } } }خود را تکرار نکنید Don’t repeat yourself (DRY)سعی کنید اصل DRY را رعایت کنید.تمام تلاش خود را بکنید تا از کپی کردن کد جلوگیری کنید.کپی تکراری بد است زیرا به این معنی است که در صورت نیاز به تغییر بخشی از منطق ، بیش از یک مکان برای تغییر چیزی وجود دارد.تصور کنید اگر رستوران اداره می کنید و موجودی خود را پیگیری می کنید: تمام گوجه فرنگی ، پیاز ، سیر ، ادویه جات، ترشی جات و غیره.اگر چندین لیست دارید که این کار را روی آنها انجام می دهید ، باید وقتی که یک ظرف را با گوجه فرنگی سرو می کنید ، همه آنها را به روز کنید.اگر فقط یک لیست دارید ، فقط یک مکان برای به روزرسانی وجود دارد!بیشتر اوقات شما کد تکراری دارید زیرا شما دو یا چند مورد کمی متفاوت دارید ،که کد های مشترک زیادی دارند ،اما تفاوتهای آنها شما را مجبور به داشتن دو یا چند توابع/متد مجزا می کند که بیشتر کارهای مشابه را انجام می دهند.حذف کد تکراری به معنای ایجاد انتزاعی(abstraction) است که می تواند تنها با یک تابع/ماژول/کلاس ، این مجموعه موارد مختلف را کنترل کند.گرفتن حق انتزاع بسیار مهم است ،به همین دلیل باید از اصول SOLID که در بخش کلاس ها ارائه شده است پیروی کنید.انتزاع بد می تواند بدتر از کد تکراری باشد ،خیلی مراقب باشید!با گفتن این حرف ، اگر می توانید انتزاع خوبی ایجاد کنید ، این کار را انجام دهید!خود را تکرار نکنید ،در غیر این صورت خودتان را در هر زمان که می خواهید یک چیز را تغییر دهید ، به روز می کنید.Bad:public List<EmployeeData> ShowDeveloperList(Developers developers) { foreach (var developers in developer) { var expectedSalary = developer.CalculateExpectedSalary(); var experience = developer.GetExperience(); var githubLink = developer.GetGithubLink(); var data = new[] { expectedSalary, experience, githubLink }; Render(data); } } public List<ManagerData> ShowManagerList(Manager managers) { foreach (var manager in managers) { var expectedSalary = manager.CalculateExpectedSalary(); var experience = manager.GetExperience(); var githubLink = manager.GetGithubLink(); var data = new[] { expectedSalary, experience, githubLink }; render(data); } }Good:public List<EmployeeData> ShowList(Employee employees) { foreach (var employee in employees) { var expectedSalary = employees.CalculateExpectedSalary(); var experience = employees.GetExperience(); var githubLink = employees.GetGithubLink(); var data = new[] { expectedSalary, experience, githubLink }; render(data); } }بهتر است از نسخه فشرده کد استفاده کنید.Very good:public List<EmployeeData> ShowList(Employee employees) { foreach (var employee in employees) { render(new[] { employee.CalculateExpectedSalary(), employee.GetExperience(), employee.GetGithubLink() }); } }منبع این بخش