اصول SOLID

اصول پنج‌گانه ی S.O.L.I.D یک سری قواعد برنامه نویسی هستند که رعایت آنها باعث خوانایی بهتر کد، افزایش قابلیت توسعه پذیری و نگهداری آن می شود. ابتدای نام هر یک از این اصول، تشکیل کلمه ی SOLID را می دهد. این اصول عبارت اند از:

  1. کاراکتر S برای Single Responsibility Principle
  2. کاراکتر O برای Open/Closed Principle
  3. کاراکتر L برای Liskov Substitution Principle
  4. کاراکتر I برای Interface Segregation Principle
  5. کاراکتر D برای Dependency Inversion Principle

در ادامه هرکدام از این اصول بررسی خواهند شد.

1. Single Responsibility Principle (اصل تک وظیفه ای)

طبق این اصل، هر کلاس تنها باید یک کار را انجام دهد. به متد زیر دقت کنید.

درست است که این کد کار می کند اما یک مشکل اساسی دارد. متد Add ، تنها وظیفه ی افزودن را بر عهده دارد، اما وظیفه ی ثبت خطا در فایل متنی را هم برعهده گرفته است، با توجه به قواعد SOLID باید این وظیفه را به کلاس دیگری سپرد.

حال می توان متد Add را اینگونه نوشت.

2. Open / Closed Principle

منظور از OCP این است که برنامه نویس باید کلاس ها و اشیاء را طوری ایجاد نماید که همواره امکان گسترش (Extend) آن وجود داشته باشد اما برای گسترش نیازی به تغییر در اصل کلاس نباشد.یعنی اینکه کدها و کلاس ها بایستی همواره برای گسترش و توسعه باز باشند اما برای ویرایش و تغییر بسته باشند. البته منظور از بسته بودن برای ویرایش این است که نیازی به تغییر کد ها نباشد.

در مثال زیر قرار است که با توجه به محصولات گوناگون، تخفیف های مختلفی لحاظ شود:

اگر بعدا ProductType جدیدی اضافه شود، مجبور به ویرایش کلاس هستیم. با پیروی از اصل Open/Close از این مشکل جلوگیری می کنیم. بدین شکل که متد GetDiscount را به صورت Virtual پیاده‌سازی کرده و اشکال مختلف آن را در قالب کلاس هایی که مشتق شده اند به صورت Override پیاده‌سازی می کنیم:

نحوه ی استفاده از آن به شکل زیر است:

3. Liskov Substitution Principle (جایگزینی لیسکوف)

اصل LSP میگوید: “زیر کلاس‌ها باید بتوانند جایگزین کلاس پایه‌ی خود باشند”. یعنی باید بتوان نمونه های زیرکلاس ها را به جای کلاس پدر به کار برد و بدون آن که برنامه به دستکاری یا تغییر نیاز داشته باشد باید بتواند مانند گذشته کار کند.

مثال:
در هندسه، ما مستطیل را یک کلاس پایه برای مربع میدانیم. به کد زیر توجه کنید :

و می توان گفت:

با توجه به LSP باید بتوانیم مستطیل را با مربع جایگزین کنیم:

چه شد؟ مگر مربع می‌تواند طول و عرض نا برابر داشته باشد؟! بدیهی است که نمی تواند. خوب این به چه معنی است؟ به این معنی که ما نمیتوانیم کلاس پایه را با کلاس مشتق شده جایگزین کنیم و باز هم این معنی را میدهد که ما اصل LSP را نقض کرده ایم. در ادامه راه حل را بررسی می کنیم.

راه حل:
یک کلاس انتزاعی (abstract) را به شکل زیر ایجاد و سپس دوکلاس Square و Rectangle را از آن مشتق میکنیم :

الان دو کلاس مستقل از هم داریم. یکی Square و دیگری Rectangle که هر دو از کلاس Shape مشتق شده اند. به پیاده سازی کلاس های مستطیل و مربع که در زیر آمده است دقت کنید:

نحوه ی استفاده از آن به شکل زیر است:

هنگام استفاده از کلاس مربع، هر عددی که به طول مربع بدهیم هم به طول و هم به عرض داده می شود. در مورد عرض هم قضیه به همین صورت است.

یک مثال دیگر در مورد اصل Liskov

به کد زیر دقت کنید، کلاس والدی به نام Product وجود دارد که دارای پارامتری از نوع Virtual به نام Discount است. دو کلاس Mobile و Tablet از این کلاس مشتق شده اند و هر کدام به شیوه ی خودشان پارامتر Discount را Override کرده اند. چون در این مثال اصل LSP رعایت شده است کلاس های Mobile و Tablet می توانند به جای کلاس والد خود یعنی Product به کار روند.

به نحوه ی استفاده از آن دقت کنید:

4. Interface Segregation Principle (تفکیک اینترفیس ها)

طبق اصل ISP کلاینت‌ها نباید وابسته به متدهایی باشند که آنها را پیاده‌سازی نمی‌کنند.
تنها متدهایی باید در اینترفیس نوشته شوند که در همه جای برنامه (هنگام استفاده از اینترفیس) کاربرد دارند. اگر قرار باشد که متدی فقط در یک جا استفاده شود نباید در اینترفیس اصلی نوشته شود چون در این صورت تمام قسمت های برنامه مجبور به پیاده‌سازی آن هستند و این خوب نیست. برای حل این مشکل، آن متدی که فقط در بعضی جاها استفاده می شود را در یک اینترفیس تعریف می کنیم و خود اینترفیس باید از اینترفیس اصلی ارث بری کند.

در کد بالا، متد RemoveAll فقط در یک جا استفاده می شود ولی تمام قسمت های برنامه باید آن را پیاده‌سازی کنند که این نقض قانون ISP است.برای حل این مشکل باید متد RemoveAll را از این اینترفیس حذف کنیم و آن را در اینترفیس جدیدی بنویسیم:

حال، هر جایی از برنامه که به متد RemoveAll احتیاجی نباشد اینترفیس IDatabaseManager را پیاده‌سازی می کند و هر جایی که به RemoveAll احتیاج داشته باشد، اینترفیس IDatabaseRemoveAll را پیاده‌سازی می کند، در این صورت به متدهای تعریف شده در اینترفیس پدر هم دسترسی دارد.

5. Dependency Inversion Principle (وارونگی وابستگی)

اصل DIP به ما میگوید که: “ماژول‌های سطح بالا نباید به ماژولهای سطح پایین وابسته باشند، هر دو باید به انتزاعات وابسته باشند. انتزاعات نباید وابسته به جزئیات باشند، بلکه جزئیات باید وابسته به انتزاعات باشند.
به جای اینکه کلاینت به کلاس ها وابسته باشد باید به انتزاع ها وابسته باشد.

در مثال زیر، کلاس Database Manager عملیات های Add, Update, Remove را انجام می دهد و گزارش کار خود را از طریق Notification اعلام می کند. چندین نوع Notification وجود دارند مانند: پیامک و ایمیل و…

طبق اصل DIP، کلاس Database Manager به ماژول سطح پایینی مثل کلاس پیامک وابسته نیست بلکه به انتزاعی به نام اینترفیس Notification وابسته است. به کد زیر دقت کنید:

کلاس Database Manager به شکل زیر است و فقط وابسته به انتزاع INotification است.

نحوه ی استفاده:

با اجرای پروژه، نتیجه ای به شکل زیر به نمایش درخواهد آمد:

Add
Notification is sent via Email
Update
Notification is sent via SMS

نوشته شده توسط mrbitmap علیرضا علی رمضانی

مقالات مرتبط

جدیدترین مقالات

فهرست