تزریق وابستگی(Dependency Injection) وارونگی وابستگی (Dependency Inversion) وارونگی کنترل(Inversion Of Control)
در این مقاله تصمیم داریم در رابطه با این سه اصطلاح که در ارتباط با هم هستند، صحبت کنیم و ابتدا مفهوم هر کدام را بررسی خواهیم کرد.
تزریق وابستگی(Dependency Injection)
در رابطه با تزریق وابستگی یا دپندنسی اینجکشن این اصطلاح یک دیزاین پترن نرم افزار است که در رابطه با ساخت و ایجاد آبجکت ها مطرح می شود و هدف از آن ایجاد و ساخت وابستگی ها در خارج از کلاس استفاده کننده و دریافت آن از روش های متداول تزریق وابستگی است همه ما می دانیم که تعریف و ایجاد وابستگی ها در داخل کلاس استفاده کننده وابستگی، یعنی یک ارتباط سفت و سخت که در آینده نه چندان دور ما را در توسعه و تغییر نرم افزار به دردسر خواهد انداخت و همیشه به دنبال راهی برای سست کردن چنین وابستگی هایی هستیم و دپندنسی اینجکشن با خارج کردن وابستگی ها از کلاسهای ما این مشکل را برای ما مرتفع می کند
روشهای تزریق وابستگی متداول عبارتند از :
هدف از دپندنسی اینجکشن قابلیت استفاده مجدد (Reusability) و قابلیت تست راحت تر است.
وارونگی کنترل(Inversion of Control)
وارونگی کنترل یکی از اصول برنامه نویسی است که جریان کنترل را در نرم افزار معکوس می کند.در برنامه نویسی پروسیجرال جریان اجرای دستورات در دست متد اصلی (Main Method) قرار دارد. ایجاد وابستگی ها، فراخوانی متد ها و حتی دریافت ورودی از کاربران که از طریق متد اصلی اتفاق می افتد و پس از اجرای دستورات از طریق همین متد به پایان می رسد.
با وارونگی کنترل (IOC) این فریم ورک است که کنترل روال ایجاد وابستگی ها، فراخوانی متد ها، دریافت ورودی کاربران و اجرا و اتمام برنامه را در دست دارد و این مسئولیت را از تابع اصلی یا اپلیکیشن حذف می کند.
با استفاده از وارونگی کنترل، وظیفه ایجاد وابستگی ها در شروع برنامه از طریق متد اصلی حذف شده و با استفاده از تنظیمات از پیش تعیین شده این ایجاد وابستگی در زمان نیاز به این وابستگی از طریق فریم ورک ایجاد خواهد شد.
وارونگی وابستگی(Dependency Inversion)
اصل وارونگی وابستگی در سال 1994 در مقاله معیار های کیفیت طراحی شی گرا (OO Design Quality Metrics) توسط روبرت مارتین (آنکل باب) مطرح شد سپس در سال 2002 در کتاب اصول، الگو ها و شیوه های توسعه نرم افزار چابک (Agile Software Development Principles, Patterns and Practices) تعریف شد.مضمون تعریف دو نکته زیر است:
-ماژول های سطح بالا(High Level Modules) نباید به ماژول های سطح پایین(Low Level Modules) وابسته باشند بلکه هر دوی این ماژول ها باید به انتزاعات(Abstraction) وابسته باشند.
- انتزاعات نباید به جزئیات وابسته باشند بلکه این جزئیات هستند که به انتزاعات وابسته اند.
در رابطه با نکته اول ماژول سطح بالا همان کلاسی است که به یک سرویس وابسته است پس کلاس، یک کلاینت برای سرویس و یک خدمت گیرنده از آن است.
حال بدون در نظر گرفتن نکته فوق اگر کلاس ما به صورت مستقیم به این سرویس وابسته باشد با کوچکترین تغییر در سرویس، کلاس ما به همراه تست های نوشته شده برای آن نیاز به تغییر خواهد داشت حال اگر این وابستگی را معکوس کنیم و نکته فوق را به کار بگیریم کلاس ما به یک اینترفیس وابستگی خواهد داشت و ماژول سطح پایین یا سرویس ما ملزم به پیاده سازی جزئیات این اینترفیس خواهد بود پس تا زمانی که بتواند اینترفیس موجود را پیاده سازی کند بدون هیچ نقصی به سرویس گیرنده خود خدمت خواهد کرد و تغییر آن هیچ تاثیری بر ماژول سطح بالا یا کلاس سرویس گیرنده نخواهد گذاشت.
در نظر داشته باشید وارونگی وابستگی نه تنها در وارونگی جهت وابستگی بلکه در وارونگی مالکیت انتزاع نیز هست.در تصویر زیر اصل وارونگی وابستگی نغض شده است و علت آن وابستگی ماژول های سطح بالا به ماژول های سطح پایین است.
بیاید با معکوس کردن وابستگی مشکل موجود را رفع کنیم.
پس از رفع مشکل موجود دستاوردی که از این انتزاع به دست می آوریم را بررسی کنیم
بیاید با یک مثال به بررسی این سه اصطلاح بپردازیم
در پروژه نمونه که کدهای مربوط به آن را می توانید از صفحه گیتهابم دریافت کنید سه نوع از وابستگی را بررسی می کنیم در پروژه اول هیچ یک از موارد بالا رعایت نشده و وابستگی های سفت و سخت بین کلاسهای پروژه قابل مشاهده است در پروژه دوم فقط وارونگی جریان کنترل به همراه تزریق وابستگی انجام شده است و در پروژه سوم تمامی موارد ذکر شده در بالا پیاده سازی شده است.
پروژه دارای یک دیتابیس لوکال اس کیو لایت با او آر ام (object-relational mapper) انتیتی فریمورک هست که شامل یک جدول ساده از اطلاعات کارمندان است
من بخش هایی از پروژه سوم را به عنوان نمونه در این مقاله قرار می دهم برای دسترسی به کد کامل پروژه ها می توانید به ریپازیتوری این پروژه در گیتهاب مراجعه کنید
در سطح اینفرا استراکچر دیتابیس اس کیو لایت برای سبکی با او آر ام انتیتی فریمورک به کار برده شده و یک جدول ساده از اطلاعات کارمندان در آن تعریف شده است
public class EmployeeContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite(@"Data Source=D:\Dependency\Dependency.sqlite");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Employee>().HasKey(x => x.Id);
modelBuilder.Entity<Employee>().Property(x => x.Id).ValueGeneratedOnAdd();
}
public DbSet<Employee> Employees { set; get; }
}
در این سطح کلاس EfEmployeeDal در بر گیرنده متد های مربوط به دیتابیس از طریق انتیتی فریمورک است که از اینترفیس IEmployeeDal ارث بری کرده است.
public class EfEmployeeDal:IEmployeeDal
{
private readonly EmployeeContext _context;
public EfEmployeeDal(EmployeeContext context)
{
_context = context;
}
public async Task Add(Employee employee)
{
await _context.Employees.AddAsync(employee);
await _context.SaveChangesAsync();
}
public async Task<List<Employee>> GetList(Expression<Func<Employee, bool>> filter = null)
{
return filter == null
? await _context.Set<Employee>().ToListAsync()
: await _context.Set<Employee>().Where(filter).ToListAsync();
}
}
اینترفیس IEmployeeDal شامل قرارداد های مورد نیاز برای سطح بالاتر هست .
public interface IEmployeeDal
{
Task Add(Employee employee);
Task<List<Employee>> GetList(Expression<Func<Employee, bool>> filter = null); }
کلاس EmployeeManager شامل متد های سطح اپلیکیشن که منطق برنامه در آن قرار دارد هست و درخواست های کاربر را بر اساس منطق برنامه با استفاده از دسترسی با اینترفیس IEmployeeDal تامین کرده و در اختیار کاربر قرار می دهد
public class EmployeeManager:IEmployeeService
{
private readonly IEmployeeDal _employeeDal;
public EmployeeManager(IEmployeeDal employeeDal)
{
_employeeDal = employeeDal;
}
public async Task<List<Employee>> GetEmployees()
{
return await _employeeDal.GetList();
}
public async Task AddEmployee(AddEmployeeDto dto)
{
await _employeeDal.Add(new Employee
{
FirstName = dto.FirstName,
LastName = dto.LastName,
InsertedDateTime = DateTime.UtcNow
});
}
}
این کلاس اینترفیس IEmployeeService را ارث بری می کند
public interface IEmployeeService
{
Task<List<Employee>> GetEmployees();
Task AddEmployee(AddEmployeeDto dto);
}
و کنترلر که درخواست های کاربر را دریافت می کند و از طریق اینترفیس IEmployeeService تامین نموده و در اختیار کاربر قرار می دهد
public class EmployeesController : ControllerBase
{
private readonly IEmployeeService _employeeService;
public EmployeesController(IEmployeeService employeeService)
{
_employeeService = employeeService;
}
[HttpGet(template: "getlist")]
public async Task<IActionResult> GetList()
{
return Ok(await _employeeService.GetEmployees());
}
[HttpPost(template: "addemployee")]
public async Task<IActionResult> AddEmployee(AddEmployeeDto dto)
{
await _employeeService.AddEmployee(dto);
return Ok();
}
}
در رابطه با لایه وارونگی جریان کنترل هم کدهای موجود در کلاس IocTool که در آن یک اکستنشن متد برای سرویس کالکشن وجود دارد، قرار داده شده
public static class IocTool
{
public static void RegisterServices(this IServiceCollection services)
{
services.AddDbContext<EmployeeContext>();
services.AddScoped<IEmployeeDal, EfEmployeeDal>();
services.AddScoped<IEmployeeService, EmployeeManager>();
}
}
و نحوه استفاده از وارونگی کنترل در کلاس پروگرم تعریف شده است
using DiWithIocAndInjectionDependency.Core;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.RegisterServices();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseAuthorization();
app.MapControllers();
app.Run();
امیدوارم توضیحاتم در رابطه با این سه اصطلاح پر کاربرد مفید واقع شود .