مفهوم Dependency Injection چیست
خلاصه
1404/09/21
Dependency Injection (DI) یا تزریق وابستگی یک الگوی طراحی (Design Pattern) در برنامه نویسی است که هدف آن کاهش وابستگی (Coupling) بین کلاسها و افزایش قابلیت تستپذیری، نگهداری
Dependency Injection (DI) یا تزریق وابستگی یک الگوی طراحی (Design Pattern) در برنامه نویسی است که هدف آن کاهش وابستگی (Coupling) بین کلاسها و افزایش قابلیت تستپذیری، نگهداری و استفاده مجدد از کد است.
**درک مفهوم وابستگی (Dependency):**
قبل از اینکه به DI بپردازیم، باید درک کنیم منظور از "وابستگی" در این زمینه چیست. یک کلاس A وقتی به کلاس B وابسته است که:
* کلاس A از کلاس B برای انجام کاری استفاده کند.
* کلاس A برای درست کار کردن، به وجود کلاس B نیاز داشته باشد.
* کلاس A یک شی (Instance) از کلاس B ایجاد کند.
**مشکلات وابستگی بالا:**
ایجاد مستقیم وابستگیها در داخل کلاسها (مانند ایجاد یک شی از کلاس دیگر در سازنده) باعث مشکلات زیر میشود:
* **Coupling بالا:** تغییر در کلاس وابسته (B) ممکن است نیازمند تغییر در کلاس استفاده کننده (A) باشد.
* **تستپذیری دشوار:** تست کردن کلاس A به طور مجزا دشوار است، زیرا نیاز به فراهم کردن کلاس B (و احتمالاً وابستگیهای آن) دارد.
* **استفاده مجدد محدود:** کلاس A نمیتواند به راحتی با پیادهسازیهای مختلف کلاس B استفاده شود.
* **نگهداری دشوار:** با افزایش پیچیدگی برنامه، مدیریت و نگهداری وابستگیها دشوار میشود.
**Dependency Injection چگونه مشکل را حل میکند؟**
DI راه حل این مشکلات را با **جداسازی (Decoupling)** فرآیند ایجاد وابستگیها از کلاس استفاده کننده ارائه میدهد. به جای اینکه کلاس مسئول ایجاد وابستگیهای خود باشد، وابستگیها به کلاس **تزریق** میشوند.
**به عبارت سادهتر:**
به جای اینکه کلاس "بپرسد" چه وابستگیهایی نیاز دارد و خودش آنها را ایجاد کند، وابستگیها به کلاس "داده میشوند".
**روشهای تزریق وابستگی:**
سه روش اصلی برای تزریق وابستگی وجود دارد:
1. **Constructor Injection (تزریق از طریق سازنده):** وابستگیها از طریق سازنده کلاس به آن تزریق میشوند. این روش رایجترین و توصیه شدهترین روش است، زیرا وابستگیها را در زمان ایجاد شی مشخص میکند و کلاس را به یک حالت معتبر میرساند.
```java
public class EmailService {
private final Logger logger;
public EmailService(Logger logger) {
this.logger = logger;
}
public void sendEmail(String message) {
logger.log("Sending email: " + message);
// ... کد ارسال ایمیل ...
}
}
// استفاده:
Logger myLogger = new MyLogger(); // یک پیادهسازی از Logger
EmailService emailService = new EmailService(myLogger);
emailService.sendEmail("Hello!");
```
در این مثال، `EmailService` به یک `Logger` وابسته است. به جای اینکه `EmailService` خودش یک `Logger` ایجاد کند، یک `Logger` از طریق سازندهاش به آن تزریق میشود.
2. **Setter Injection (تزریق از طریق متد Set):** وابستگیها از طریق متدهای Set یا Properties بعد از ایجاد شی، تزریق میشوند.
```java
public class EmailService {
private Logger logger;
public void setLogger(Logger logger) {
this.logger = logger;
}
public void sendEmail(String message) {
logger.log("Sending email: " + message);
// ... کد ارسال ایمیل ...
}
}
// استفاده:
EmailService emailService = new EmailService();
Logger myLogger = new MyLogger();
emailService.setLogger(myLogger);
emailService.sendEmail("Hello!");
```
این روش انعطافپذیرتر است، اما ممکن است کلاس در ابتدا در یک حالت نامعتبر قرار داشته باشد (یعنی `logger` برابر `null` باشد) تا زمانی که متد `setLogger` فراخوانی شود.
3. **Interface Injection (تزریق از طریق Interface):** کلاس یک رابط (Interface) را پیادهسازی میکند که متدی برای تزریق وابستگی دارد.
```java
public interface LoggerAware {
void setLogger(Logger logger);
}
public class EmailService implements LoggerAware {
private Logger logger;
@Override
public void setLogger(Logger logger) {
this.logger = logger;
}
public void sendEmail(String message) {
logger.log("Sending email: " + message);
// ... کد ارسال ایمیل ...
}
}
// استفاده:
EmailService emailService = new EmailService();
Logger myLogger = new MyLogger();
emailService.setLogger(myLogger);
emailService.sendEmail("Hello!");
```
این روش کمتر رایج است و بیشتر در مواردی استفاده میشود که یک کلاس نیاز به پشتیبانی از تزریق چند وابستگی مختلف دارد.
**مزایای Dependency Injection:**
* **Coupling کمتر:** وابستگی بین کلاسها کاهش مییابد و تغییرات در یک کلاس کمتر احتمال دارد بر سایر کلاسها تأثیر بگذارد.
* **تستپذیری بهتر:** تست کردن کلاسها به طور مجزا آسانتر است، زیرا میتوان وابستگیها را با Mock یا Stub جایگزین کرد.
* **قابلیت استفاده مجدد بیشتر:** کلاسها میتوانند به راحتی با پیادهسازیهای مختلف وابستگیها استفاده شوند.
* **نگهداری آسانتر:** مدیریت و نگهداری وابستگیها آسانتر میشود، به خصوص در برنامههای بزرگ و پیچیده.
* **انعطافپذیری بیشتر:** به راحتی میتوان وابستگیها را در زمان اجرا تغییر داد.
* **پیروی از اصول SOLID:** DI به پیروی از اصل *Dependency Inversion* در SOLID کمک میکند.
**معایب Dependency Injection:**
* **پیچیدگی بیشتر کد:** کد میتواند کمی پیچیدهتر شود، به خصوص در ابتدا.
* **نیاز به Container (اختیاری):** برای مدیریت وابستگیها، ممکن است نیاز به استفاده از یک Dependency Injection Container باشد. (در ادامه توضیح داده خواهد شد)
**Dependency Injection Container (DI Container):**
یک DI Container (یا IoC Container) یک فریمورک یا کتابخانه است که وظیفه مدیریت و تزریق وابستگیها را به عهده دارد. به جای اینکه شما دستی وابستگیها را ایجاد و تزریق کنید، DI Container این کار را به صورت خودکار انجام میدهد.
DI Containerها معمولاً از فایلهای پیکربندی (XML، YAML، JSON و غیره) یا Annotationها برای مشخص کردن وابستگیها استفاده میکنند.
**برخی از DI Containerهای معروف:**
* **Java:** Spring Framework, Guice, Dagger
* **.NET:** Autofac, Ninject, Microsoft.Extensions.DependencyInjection
* **PHP:** Symfony Dependency Injection, Laravel Service Container
* **Python:** Injector, Dependency Injector
**مثال استفاده از DI Container (Spring Framework در Java):**
```java
// EmailService.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class EmailService {
private final Logger logger;
@Autowired
public EmailService(Logger logger) {
this.logger = logger;
}
public void sendEmail(String message) {
logger.log("Sending email: " + message);
// ... کد ارسال ایمیل ...
}
}
// Logger.java
import org.springframework.stereotype.Component;
@Component
public class MyLogger implements Logger {
@Override
public void log(String message) {
System.out.println("Log: " + message);
}
}
// Application.java
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan
public class Application {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
EmailService emailService = context.getBean(EmailService.class);
emailService.sendEmail("Hello from Spring!");
}
}
```
در این مثال:
* `@Component` و `@Autowired` Annotationها از Spring Framework استفاده میشوند.
* `@Component` به Spring میگوید که کلاس `EmailService` و `MyLogger` را به عنوان Bean (شیء مدیریت شده توسط Container) در نظر بگیرد.
* `@Autowired` به Spring میگوید که یک `Logger` را به سازنده `EmailService` تزریق کند.
* `ApplicationContext` یک DI Container از Spring است. `getBean(EmailService.class)` یک شیء از `EmailService` را از Container دریافت میکند. Spring به صورت خودکار وابستگیهای `EmailService` را مدیریت میکند (در این مورد، `MyLogger`).
**چه زمانی از Dependency Injection استفاده کنیم؟**
* هنگامی که میخواهید Coupling بین کلاسها را کاهش دهید.
* هنگامی که میخواهید تستپذیری کد خود را بهبود بخشید.
* هنگامی که میخواهید قابلیت استفاده مجدد از کد خود را افزایش دهید.
* در برنامههای بزرگ و پیچیده که مدیریت وابستگیها اهمیت دارد.
* در پروژههایی که از اصول SOLID پیروی میکنند.
**خلاصه:**
Dependency Injection یک الگوی طراحی قدرتمند است که به شما کمک میکند کد بهتری بنویسید: کد با Coupling کمتر، تستپذیری بیشتر، قابلیت استفاده مجدد بیشتر و نگهداری آسانتر. در حالی که ممکن است در ابتدا کمی پیچیده به نظر برسد، با درک اصول و استفاده از DI Containerها، میتوانید از مزایای آن در پروژههای خود بهرهمند شوید.
برخی از محصولات شرکت مهندسی آبان رایان البرز
سایر مقالات آموزشی شرکت نرم افزاری آبان رایان البرز :
- چگونه یک پروژه نرمافزاری را مستند کنیم
- واحد تست چیست و چگونه طراحی میشود
- فایده استفاده از Breakpoint در اشکالزدایی چیست
- الگوریتم مرتبسازی سریع Quick Sort چگونه عمل میکند
- اصول اولیه طراحی فرمهای ورودی در نرمافزار چیست
- چگونه میتوان در SQL چند جدول را همزمان کوئری گرفت
- تفاوت بین int و float در زبانهای برنامهنویسی چیست
- کامپایل در برنامهنویسی چه نقشی دارد
- چگونه پایگاه داده را در ساختار میکروسرویس پیادهسازی کنیم
- نقش معماری میکروسرویس در توسعه نرمافزار چیست
- مدیریت ترافیک شبکه در سیستمهای نرمافزاری چگونه انجام میشود
- نقش رایانش مرزی Edge Computing در آینده چیست
- چگونه یک سیستم پشتیبانگیری خودکار طراحی کنیم
- چگونه خطاهای پایگاه داده را بررسی و رفع کنیم
- چه ابزارهایی برای تست عملکرد پایگاه داده وجود دارد
- چگونه از بروز تضاد در دادهها جلوگیری کنیم