
ممکن است گیج کننده به نظر برسد !!!
مطمئناً من هم وقتی برای اولین بار آن را خواندم، برای من هم گیج کننده بود. اما به شما اطمینان خواهم داد (100%) با مطلالعه این مطلب تا انتها برای همیشه آن را یاد خواهید گرفت اصلا نگران آن نباشید...
ایده کلی این اصل به همان اندازه که مهم است ساده است: کلاس های سطح بالا، که منطق پیچیده ای را ارائه می دهند، باید به راحتی قابل استفاده مجدد باشند و تحت تأثیر تغییرات در کلاس های سطح پایین، که ویژگی های کاربردی را ارائه می دهند، قرار نگیرند. برای رسیدن به آن، باید انتزاعی را معرفی کنید که کلاس های سطح بالا و سطح پایین را از یکدیگر جدا می کند.
در نتیجه اگر اصل OCP و اصل LSP را در کد خود اعمال کنید، از اصل DIP نیز پیروی خواهد کرد.
اصل OCP نیازمند آن بود که یک جزء نرم افزار برای توسعه باز باشد، اما برای اصلاح بسته شود. میتوانید با معرفی رابطهایی که میتوانید پیادهسازیهای مختلفی برای آنها ارائه کنید، به آن دست پیدا کنید. خود اینترفیس برای تغییر بسته است و شما به راحتی می توانید با ارائه یک پیاده سازی رابط جدید آن را گسترش دهید.
پیادهسازیهای شما باید از اصل LSP پیروی کنند تا بتوانید آنها را با سایر پیادهسازیهای همان رابط جایگزین کنید بدون اینکه برنامهتان خراب شود.
پیاده سازی (Implementation)
خوب همچنان کمی گیج کننده است !!! بیایید از اول و با یک مثال عملی آن را بررسی کنیم
یک App ساده ماشین حساب را در نظر بگیرید. پیاده سازی اولیه آن می تواند به سادگی مورد زیر باشد:
public class Calculator { public double Add(double x, double y) { return x + y; } public double Subtract(double x, double y) { return x - y; } }
کلاس Calculator ساده بالا با دو عمل کار می کند، اما اگر بخواهیم یک عملیات جدید اضافه کنیم، مثلا Multiply، چی؟ افزودن یک عملیات جدید به کلاس ماشین حساب فعلی ما، کلاس فعلی را تغییر می دهد. این اصلاح اصلOCP را در S.O.L.I.D نقض می کند. OCP بیان می کند که:موجودیت های نرم افزاری باید برای توسعه باز باشند، اما برای اصلاح بسته باشند.
بنابراین چگونه میتوانیم عملیات جدید (یا «توسعه») کلاس ماشین حساب خود را بدون انجام تغییرات کد (یا «اصلاحات») به آن اضافه کنیم؟ اینجاست که اصل وارونگی وابستگی (DIP) وارد عمل می شود.
بیایید در مورد دومین موردی که DIP بیان می کند صحبت کنیم:
انتزاع ها نباید به جزئیات بستگی داشته باشند. جزئیات باید به انتزاعات بستگی داشته باشد.
هدفی که در حال حاضر می خواهیم اضافه کردن یک عملیات جدید به کلاس Calculator است. با نگاهی به روشهای قبلی Add and Subtract میتوانیم با خیال راحت بگوییم که میتوانیم یک رابط(interface) یا قراردادی(contract) ایجاد کنیم که ماشین حساب ما از آن استفاده میکند.
public interface ICalculatorOperation { double Calculate(double x, double y); }
رابط بالا در واقع "انتزاعی" است که ماشین حساب ما از آن استفاده می کند. "جزئیات" پیاده سازی واقعی رابط هستند.
بیایید اکنون کلاس Calculator خود را مجدداً تغییر دهیم تا در واقع از ICalculatorOperation استفاده کنیم. ما قصد داریم از تکنیک تزریق وابستگی(Dependency Injection) از طریق Constructor Injection استفاده کنیم.
خیلی خلاصه و کوتاه Dependency Injection چیست؟ جایی است که وابستگیهای یک شی توسط یک چارچوب یا یک سرویس ارائه میشود. این یک پیاده سازی از اصل DIP است.
برای درک بهتر این موضوع، قطعه کد بازسازی شده را در زیر ببینید
نکته: ICalculatorOperation از طریق سازنده "تزریق" می شود.
این تضمین می کند که ماشین حساب نمی تواند بدون calculatorOperation ایجاد شود.
این باعث می شود که ماشین حساب به ICalculatorOperation "وابسته" شود.
public class Calculator { private ICalculatorOperation CalculatorOperation; public Calculator(ICalculatorOperation calculatorOperation) { CalculatorOperation = calculatorOperation; } public double Solve(double x, double y) { // محاسبات بر اساس //ICcalculatorOperation // تزریق شده خواهد بود. return CalculatorOperation.Calculate(x, y); } }
برای افزودن عملیات «افزودن» و «تفریق» قبلی خود، فقط باید دو شی ایجاد کنیم که رابط/قرارداد ICalculatorOperation ما را پیادهسازی کند:
public class AddCalculatorOperation : ICalculatorOperation { public double Calculate(double x, double y) { return x + y; } } public class SubtractCalculatorOperation : ICalculatorOperation { public double Calculate(double x, double y) { return x - y; } }
با کد فعلی ما، اکنون به راحتی میتوان «توسعه» داد و عملیاتهای جدید را به کلاس Calculator خود بدون «تغییر» آن اضافه کرد. بیایید دو عملیات جدید اضافه کنیم:
public class DivideCalculatorOperation : ICalculatorOperation { public double Calculate(double x, double y) { return x / y; } } public class MultiplyCalculatorOperation : ICalculatorOperation { public double Calculate(double x, double y) { return x * y; } }
در اینجا یک قطعه کد نمونه در مورد نحوه عملکرد برنامه ساده ما آمده است:
public class CalculatorProgram { public void AddSample() { Calculator calculator = new Calculator(new AddCalculatorOperation()); double result = calculator.Solve(1, 1); // Result is 2. } public void SubtractSample() { Calculator calculator = new Calculator(new SubtractCalculatorOperation()); double result = calculator.Solve(1, 1); // Result is 0. } public void MultiplySample() { Calculator calculator = new Calculator(new MultiplyCalculatorOperation()); double result = calculator.Solve(1, 2); // Result is 2. } public void DivideSample() { Calculator calculator = new Calculator(new DivideCalculatorOperation()); double result = calculator.Solve(10, 5); // Result is 2. } }
با کد فعلی ما، اکنون توضیح اصل وارونگی وابستگی (DIP) آسان تر است. ما از تکنیک تزریق وابستگی (DI) که پیادهسازی DIP است برای اعمال اصل OCP استفاده کردیم. کلاس Calculator ما اکنون به انتزاع ما متکی است که ICalculatorOperation است نه اجرای خاص ICalculatorOperation.
بیایید اکنون در مورد اولین بیانیه در DIP صحبت کنیم:
ماژول های سطح بالا نباید به ماژول های سطح پایین وابسته باشند. هر دو باید به انتزاعات بستگی داشته باشند.
با استفاده از نمونه برنامه فوق، اکنون می دانیم که ماژول سطح بالایی که به آن اشاره می کنیم کلاس Calculator است در حالی که ماژول های سطح پایین کلاس های مختلف (AddCalculatorOperation، SubtractCalculatorOperation، MultiplyCalculatorOperation، DivideCalculatorOperation) هستند که ICalculatorOperation ما را اجرا می کنند.
اما چرا به آنها سطح بالا و سطح پایین می گویند؟ برای توضیح آسان این موضوع، باید یک نمودار UML برای این یکی ایجاد کنیم:

همانطور که می بینید، ماشین حساب در قسمت بالاتر نمودار قرار دارد در حالی که عملیات ماشین حساب در قسمت پایین نمودار قرار دارد.

بنابراین Dependency Injection پیاده سازی Dependency Inversion Principle است.
یکی از راه های دستیابی به اصل OCP، استفاده از اصل DIP است.
ماژول های سطح بالا در DIP ماژول هایی هستند که در قسمت بالاتر نمودار UML ظاهر می شوند و به لایه انتزاعی بستگی دارند. انتزاع ها آنهایی هستند که در مرکز نمودار قرار دارند. ماژول های سطح پایین آنهایی هستند که در سطح پایین نمودار قرار دارند و پیاده سازی واقعی لایه انتزاعی هستند.
این اصل به ما می گوید نباید در کدمان Coupling داشته باشیم
تعريف Coupling : یک مقیاس اندازه گیری است برای اینکه بفهمیم دو کلاس چقدر با هم در ارتباط هستند و چقدر روی هم تاثیر می گذارند .بهتر است که کلاسها با هم ارتباط کمتری داشته باشند و در اصل ما دوست داریم که در مدلمان Coupling کم(Low Coupling) باشد.
بیشتر بخوانید : اصول طراحی نرم افزار Software Design Principles