به عنوان برنامهنویس، ما همیشه با سیستمهایی سروکار داریم که از چندین کلاس و کامپوننت تشکیل شدهاند.
هر کلاس معمولاً دو نوع مسئولیت دارد:
منطق اصلی خودش (Business Logic)
استفاده از سرویسها یا کلاسهای دیگر برای انجام وظایف جانبی
برای درک بهتر، بیایید یک مثال واقعی را بررسی کنیم 👇
فرض کنید کلاسی داریم به نام OrderService که مسئول مدیریت سفارشهاست.
اما برای ثبت هر سفارش، نیاز داریم پرداخت (Payment) هم انجام شود.
سادهترین حالت ممکن این است که OrderService خودش مستقیماً یک نمونه از PaymentService بسازد:
public class OrderService { private PaymentService _paymentService = new PaymentService(); public void PlaceOrder(Order order) { _paymentService.Process(order); } } public class PaymentService { public void Process(Order order) { Console.WriteLine("پرداخت سفارش انجام شد."); } }
در نگاه اول ساده به نظر میرسد، اما در واقع یک مشکل جدی وجود دارد 👇OrderService به شدت به PaymentService وابسته است.
اگر بخواهیم نوع پرداخت را تغییر دهیم (مثلاً PayPal یا Stripe اضافه کنیم) یا در تستها از نسخهی Fake استفاده کنیم،
باید داخل کد OrderService تغییر دهیم — این یعنی وابستگی شدید (Tight Coupling).
اینجاست که مفاهیم Inversion of Control (IoC)، Dependency Injection (DI) و Dependency Inversion Principle (DIP) وارد میشوند.
در طراحی سنتی، هر کلاس خودش کنترل ساخت وابستگیها را دارد.
اما در IoC، این کنترل وارونه میشود — یعنی ساخت و مدیریت وابستگیها از درون کلاس گرفته میشود و به بیرون سپرده میشود.
به زبان ساده:
به جای اینکه کلاس بگوید "من میسازم و استفاده میکنم"، میگوید "به من بده تا استفاده کنم".
public class OrderService { public void PlaceOrder(Order order) { var paymentService = new PaymentService(); paymentService.Process(order); } }
در اینجا کنترل ساخت درون OrderService است، یعنی خودش تصمیم میگیرد از چه نوع PaymentService استفاده کند.
public class OrderService { private readonly IPaymentService _paymentService; public OrderService(IPaymentService paymentService) { _paymentService = paymentService; } public void PlaceOrder(Order order) { _paymentService.Process(order); } }
و حالا فقط یک interface داریم که قرارداد (contract) را مشخص میکند:
public interface IPaymentService { void Process(Order order); }
و چند پیادهسازی مختلف:
public class CreditCardPaymentService : IPaymentService { public void Process(Order order) { Console.WriteLine("پرداخت با کارت اعتباری انجام شد."); } } public class PayPalPaymentService : IPaymentService { public void Process(Order order) { Console.WriteLine("پرداخت با PayPal انجام شد."); } }
در این حالت، OrderService فقط میگوید "من به یک IPaymentService نیاز دارم"،
و دیگر خودش تصمیم نمیگیرد از چه نوعی استفاده کند — این یعنی کنترل معکوس شده است (Inversion of Control).
Dependency Injection یکی از روشهای پیادهسازی IoC است.
در این روش، وابستگیها از بیرون (مثلاً از طریق constructor) تزریق میشوند.
در مثال بالا، وقتی برنامه اجرا میشود، یک IoC Container نمونهی مناسب از IPaymentService را ساخته و به OrderService تزریق میکند:
var payment = new CreditCardPaymentService(); var orderService = new OrderService(payment); orderService.PlaceOrder(new Order());
در ASP.NET Core این کار بهصورت خودکار و با ثبت در Startup.cs انجام میشود:
public void ConfigureServices(IServiceCollection services) { services.AddScoped<IPaymentService, CreditCardPaymentService>(); services.AddScoped<OrderService>(); }
حالا اگر خواستیم از PayPal استفاده کنیم، فقط باید ثبت را تغییر دهیم:
services.AddScoped<IPaymentService, PayPalPaymentService>();
بدون اینکه حتی یک خط از OrderService را تغییر دهیم.
این یعنی کاهش وابستگی (Loose Coupling) و افزایش انعطافپذیری.
اصل پنجم از SOLID میگوید:
ماژولهای سطح بالا نباید به ماژولهای سطح پایین وابسته باشند.
هر دو باید به Abstraction (interface یا abstract class) وابسته باشند.
در مثال ما:
OrderService یک ماژول سطح بالا است (منطق اصلی).
CreditCardPaymentService یا PayPalPaymentService ماژولهای سطح پایین هستند (جزئیات).
هر دو به IPaymentService وابستهاند (Abstraction).
بنابراین:
اگر روش پرداخت تغییر کند، نیازی نیست OrderService تغییر کند.
جزئیات وابسته به abstraction هستند، نه برعکس.
این یعنی اصل Dependency Inversion کاملاً رعایت شده است.
در سیستمهای واقعی با دهها یا صدها کلاس و سرویس، ساخت دستی وابستگیها دشوار میشود.
اینجاست که Type Broker یا همان IoC Container وارد عمل میشود.
نقش Type Broker:
ثبت سرویسها (Registration)
ساخت و تزریق خودکار وابستگیها (Resolution)
مدیریت طول عمر اشیاء (Lifetime)
مثال در ASP.NET Core:
public void ConfigureServices(IServiceCollection services) { services.AddTransient<IPaymentService, PayPalPaymentService>(); services.AddScoped<OrderService>(); }
وقتی OrderService فراخوانی شود، سیستم بهصورت خودکار:
PayPalPaymentService را میسازد،
آن را در سازندهی OrderService تزریق میکند،
و طول عمر آن را مطابق تنظیمات مدیریت میکند.
به همین دلیل از آن به عنوان Type Broker یا Dependency Resolver یاد میشود.
مفهومتوضیحنقشIoCوارونگی کنترل ساخت وابستگیهافلسفه و ایدهDIروش اجرای IoC از طریق تزریق وابستگیهاپیادهسازیDIPاصل طراحی بر اساس abstraction، نه implementationقانون طراحیType Broker / IoC Containerابزار مدیریت و تزریق خودکار وابستگیهامکانیزم اجرایی
✅ کاهش وابستگی (Loose Coupling) — کلاسها به abstraction وابستهاند، نه به همدیگر.
✅ افزایش تستپذیری (Testability) — میتوان Mock یا Fake تزریق کرد.
✅ انعطافپذیری بالا (Flexibility) — تغییر در جزئیات بدون تغییر در منطق اصلی.
✅ نگهداری آسان (Maintainability) — Dependencyها متمرکز و قابل کنترل هستند.
✅ مقیاسپذیری (Scalability) — با وجود Type Broker، کل سیستم منظم و قابل پیشبینی میماند.
در معماری مدرن، مفاهیم IoC، DI، DIP و Type Broker مکمل یکدیگر هستند.
با بهکارگیری آنها:
وابستگیها قابل کنترل میشوند،
کدها قابل تست و نگهداری میمانند،
و سیستم بهصورت ماژولار و مقیاسپذیر رشد میکند.
به زبان ساده:
🔸 DIP میگوید: به abstraction وابسته شو.
🔸 IoC میگوید: کنترل ساخت را به بیرون بده.
🔸 DI میگوید: بگذار من تزریقش کنم.
🔸 Type Broker میگوید: من کل این فرآیند را برایت مدیریت میکنم.