در زمان حال توسعه نرمافزارها، به دلیل پیچیدگیها و تغییرات ممکن در نیازها، استفاده از Dependency Injection (DI) به عنوان یک الگوی طراحی اساسی و اصولی، به ما کمک میکند تا کد خود را تمیزتر، قابل اطمینانتر، و قابل تستتر کنیم. در این مقاله، به بررسی روشهای استفاده از Dependency Injection با استفاده از Node.js میپردازیم و نحوهی پیادهسازی آن را به صورتی ساده و قابل فهم توضیح خواهیم داد.
فهرست مطالب
در این مقاله، به موارد زیر خواهیم پرداخت:
Dependency Injection چیست؟
مزایا و معایب Dependency Injection
نحوه استفاده از Dependency Injection با Node.js
3.1. نصب و راهاندازی پروژه Node.js
3.2. تعریف وابستگیها
3.3. تزریق وابستگیها
3.4. مثال عملی Dependency Injection در Node.js
3.5. تزریق سازنده
3.6. تزریق Setter
3.7 تزریق Property
استفاده از کتابخانههای Dependency Injection
4.1. نصب InversifyJS
موارد توصیهشده برای استفاده از Dependency Injection
مزیتهای استفاده از IoC (Inversion of Control)
مقایسه Dependency Injection با Service Locator
نکات بهینهسازی کاربرد Dependency Injection
تطبیق Dependency Injection با Design Patterns
1. Dependency Injection چیست؟
Dependency Injection یک الگوی طراحی است که باعث میشود کلاسها و اجزای سیستم ما به جای ایجاد وابستگیهای خود، آنها را دریافت کنند. به عبارت دیگر، وابستگیها به جای تولید داخل کلاس، تزریق میشوند. این الگو باعث کاهش اتصال بین کلاسها و اجزاء میشود و از اصطلاح "Inversion of Control (IoC)" نیز استفاده میکند.
2. مزایا و معایب Dependency Injection
مزایا:
کاهش وابستگیها: استفاده از Dependency Injection باعث کاهش وابستگیهای کلاسها و اجزاء میشود و امکان انتقال یک وابستگی به یک کلاس دیگر را فراهم میکند.
قابلیت تست: با استفاده از این الگو، تستهای واحد راحتتر و ممکن میشود؛ زیرا میتوان وابستگیها را جایگزینی کرد و بهطور مصنوعی به آنها مقدار دلخواه داد.
افزایش خوانایی: کدی که از Dependency Injection استفاده میکند، خوانایی بالاتری دارد؛ زیرا وابستگیها بهوضوح تعریف و دیده میشوند.
معایب:
پیچیدگی: گاهی اوقات استفاده از Dependency Injection ممکن است پیچیدگی کد را افزایش دهد و برخی مشکلات مرتبط با تنظیمات وابستگیها ایجاد کند.
هزینهبر بودن: استفاده از این الگو ممکن است منجر به ایجاد کدهای اضافی و هزینهبر شود که باید با مزایای آن مقایسه شود.
3. نحوه استفاده از Dependency Injection با Node.js
حال که با مفاهیم اساسی Dependency Injection آشنا شدیم، به بررسی نحوه استفاده از آن با Node.js میپردازیم.
3.1. نصب و راهاندازی پروژه Node.js
ابتدا باید مطمئن شویم که Node.js روی سیستم ما نصب شده است. سپس با دستورات زیر یک پروژه جدید راهاندازی میکنیم:
mkdir MyDependencyInjectionProject
cd MyDependencyInjectionProject
npm init -y
3.2. تعریف وابستگیها
حالا که پروژهمان آماده است، باید وابستگیهایی که میخواهیم تزریق کنیم را تعریف کنیم. این وابستگیها میتوانند کلاسها، ماژولها یا هر نوع اطلاعات دیگری باشند. بهعنوان مثال، فرض کنید یک سرویس UserService داریم که مسئول مدیریت کاربران است:
// UserService.js
class UserService {
constructor() {
// اضافه کردن ویژگیها و توابع مورد نیاز
}
// توابع مورد نیاز برای مدیریت کاربران
}
module.exports = UserService;
3.3. تزریق وابستگیها
حالا که کلاس UserService را تعریف کردیم، میتوانیم آن را به عنوان یک وابستگی در کلاسها یا ماژولهای دیگر تزریق کنیم. این کار با استفاده از Constructor Injection یا Setter Injection انجام میشود.
// UserRepository.js
class UserRepository {
constructor(userService) {
this.userService = userService;
}
// توابع مورد نیاز برای انجام عملیات مرتبط با پایگاه داده کاربران
}
module.exports = UserRepository;
3.4. مثال عملی Dependency Injection در Node.js
بیایید یک مثال عملی از Dependency Injection در Node.js را ببینیم. فرض کنید یک برنامه ساده مدیریت کاربران داریم. برای این منظور، از کلاسهای UserService و UserRepository استفاده میکنیم:
// index.js
const UserService = require('./UserService');
const UserRepository = require('./UserRepository');
// تزریق وابستگیها
const userService = new UserService();
const userRepository = new UserRepository(userService);
// استفاده از کلاسهای تزریق شده
// ...
حال که تزریق وابستگی ها را درک کردیم بیاید روش های دیگر از تزریق وابستگی ها آشنا شویم.
برای پیادهسازی تزریق وابستگی در Node.js، میتوانیم از تزریق سازنده، تزریق Setter، و تزریق Property استفاده کنیم. در این روشها، وابستگیها به عنوان پارامترها در کلاسها یا توابع دریافت میشوند و به صورت دستی تزریق میشوند.در اینجا به صورت خلاصه به توضیح هر یک از این روشها میپردازیم:
3.5. تزریق سازنده
در این روش، وابستگیها به عنوان پارامترهای سازنده کلاس دریافت میشوند و در داخل کلاس ذخیره میشوند. به عبارت دیگر، در زمان ایجاد شیء کلاس، وابستگیها تزریق میشوند. مثالی از تزریق سازنده به شکل زیر است:
class DatabaseService {
constructor() {
// اتصال به دیتابیس
}
getUsers() {
// دریافت لیست کاربران از دیتابیس
}
// ...
}
class UserService {
constructor(databaseService) {
this.databaseService = databaseService;
}
getAllUsers() {
return this.databaseService.getUsers();
}
// ...
}
// ساخت شیء کلاس DatabaseService
const databaseService = new DatabaseService();
// ساخت شیء کلاس UserService و تزریق وابستگی (databaseService) از طریق سازنده
const userService = new UserService(databaseService);
// استفاده از کلاس UserService
const users = userService.getAllUsers();
3.6. تزریق Setter
در تزریق ستر، وابستگیها به عنوان پارامترهای متدهای کلاس دریافت میشوند و در داخل کلاس ذخیره میشوند. این روش از تزریق سازنده کمی متفاوت است که در آن وابستگیها به صورت جداگانه در متدهای کلاس تزریق میشوند. مثالی از تزریق ستر به شکل زیر است:
class LoggerService {
log(message) {
// ثبت پیام در سیستم لاگگیری
}
// ...
}
class MailerService {
setLoggerService(loggerService) {
this.loggerService = loggerService;
}
sendEmail(to, subject, body) {
// ارسال ایمیل و ثبت لاگها با استفاده از loggerService
this.loggerService.log(`ایمیل به ${to} با موضوع "${subject}" ارسال شد.`);
}
// ...
}
// ساخت شیء کلاس LoggerService
const loggerService = new LoggerService();
// ساخت شیء کلاس MailerService و تزریق وابستگی (loggerService) از طریق متد setLoggerService
const mailerService = new MailerService();
mailerService.setLoggerService(loggerService);
// استفاده از کلاس MailerService
mailerService.sendEmail('example@example.com', 'سلام', 'این یک ایمیل تستی است.');
3.7. تزریق Property
در تزریق Property ، وابستگیها به عنوان پارامترهای متدهای کلاس دریافت نمیشوند و به صورت مستقیم در کلاس ست میشوند. به عبارت دیگر، در زمان ایجاد شیء کلاس، وابستگیها تزریق میشوند. مثالی از تزریق Property به شکل زیر است:
class ApiService {
// ویژگیهای کلاس
databaseService;
loggerService;
setDatabaseService(databaseService) {
this.databaseService = databaseService;
}
setLoggerService(loggerService) {
this.loggerService = loggerService;
}
fetchData() {
// دریافت دادهها از سرویس دیتابیس و ثبت لاگها با استفاده از loggerService
this.loggerService.log('دادهها دریافت شدند.');
return this.databaseService.getData();
}
// ...
}
// ساخت شیء کلاس ApiService
const apiService = new ApiService();
// تزریق وابستگیها (databaseService و loggerService) به ویژگیهای کلاس
apiService.setDatabaseService(databaseService);
apiService.setLoggerService(loggerService);
// استفاده از کلاس ApiService
const data = apiService.fetchData();
4. استفاده از کتابخانههای Dependency Injection
برای پیادهسازی Dependency Injection در Node.js، میتوان از کتابخانههای مختلفی مانند "InversifyJS"، "Awilix" یا "Typedi" استفاده کرد. این کتابخانهها ابزارهای قدرتمندی هستند که تسهیلکنندههای عالی برای استفاده از Dependency Injection در برنامههای Node.js ارائه میدهند.
بیاید با هم دیگر با کتابخانه InversifyJS استفاده کنیم:
4.1. نصب InversifyJS
ابتدا باید کتابخانه InversifyJS را نصب کنیم:
npm install inversify reflect-metadata --save
4.2. تعریف وابستگیها
ابتدا باید وابستگیها را تعریف کنیم. به عنوان مثال:
import { injectable, inject } from 'inversify';
@injectable()
class DatabaseService {
// کدهای مربوط به سرویس دیتابیس
}
@injectable()
class LoggerService {
// کدهای مربوط به سرویس لاگگیری
}
4.3. تعریف کلاس اصلی
حالا کلاس اصلی را تعریف میکنیم و وابستگیها را به عنوان پارامترها دریافت میکنیم:
import { injectable, inject } from 'inversify';
@injectable()
class App {
constructor(
@inject(DatabaseService) private databaseService: DatabaseService,
@inject(LoggerService) private loggerService: LoggerService,
) {
// کدهای مربوط به کلاس اصلی
}
}
4.4. ثبت وابستگیها و راهاندازی کلاس اصلی
در نهایت، وابستگیها را در یک کانتینر ثبت میکنیم و کلاس اصلی را راهاندازی میکنیم:
import { Container } from 'inversify';
const container = new Container();
container.bind<DatabaseService>(DatabaseService).to(DatabaseService);
container.bind<LoggerService>(LoggerService).to(LoggerService);
const appInstance = container.resolve(App);
5. موارد توصیهشده برای استفاده از Dependency Injection
برای استفاده موثر از Dependency Injection در پروژههای Node.js، موارد زیر را رعایت کنید:
تنها وابستگیهای لازم را تزریق کنید و از افزونگیها وابستهسازی اجتناب کنید.
از کتابخانهها و توابع تزریقی استفاده کنید تا کد شما تمیزتر و خواناتر باشد.
توجه داشته باشید که تعداد زیادی وابستگی منجر به پیچیدگی زیادی میشود و از مزایای Dependency Injection کاسته میشود.
6. مزیتهای استفاده از IoC (Inversion of Control)
استفاده از Dependency Injection به ما امکان میدهد کنترل کلی برنامه را به صورت کامل تغییر دهیم و کد را بهبود بخشیم. با این روش، اصطلاح "Inversion of Control (IoC)" معنی واقعی خود را پیدا میکند.
7. مقایسه Dependency Injection با Service Locator
Dependency Injection و Service Locator هر دو الگوهای طراحی برای مدیریت وابستگیها هستند، اما با رویکردهای متفاوت. انتخاب بین این دو بستگی به نیازها و موقعیتهای خاص پروژه شما میباشد.
8. نکات بهینهسازی کاربرد Dependency Injection
برای بهینهسازی استفاده از Dependency Injection، به برخی نکات توجه کنید:
استفاده از کتابخانهها و فریمورکهای مناسب
تزریق کلاسها وابسته به سطح اجرا (Runtime) به جای زمان کامپایل (Compile Time)
9. تطبیق Dependency Injection با Design Patterns
استفاده از Dependency Injection با برخی از الگوهای طراحی معروف مانند Factory و Singleton میتواند کدهای شما را بهبود بخشد و قابلیت تغییرپذیری را افزایش دهد.
نتیجهگیری
در این مقاله، ما با مفاهیم اساسی Dependency Injection آشنا شدیم و نحوه استفاده از این الگو در Node.js را بررسی کردیم. همچنین به مزایا و معایب این الگو پرداختیم و مثالهای واقعی در پروژههای معروف را مورد بررسی قرار دادیم. با رعایت موارد توصیهشده و اجتناب از اشتباهات رایج، میتوانید استفاده موثری از Dependency Injection در پروژههای خود داشته باشید.