Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> C#

วิธีทำ Dependency Injection แบบต่างๆ และข้อดีใน C#

Dependency Injection (DI) เป็น Design Pattern ที่แยกการสร้าง Dependency (Object ที่ต้องการ) ออกจาก Class ที่ใช้ Dependency เพื่อลด Coupling และเพิ่ม Testability/Maintainability

4 ประเภทหลักของ DI

ประเภท วิธีส่ง Dependency เหมาะสำหรับ
Constructor Injection ผ่าน Constructor Parameter Mandatory Dependencies (ส่วนใหญ่)
Setter/Property Injection ผ่าน Public Property (Set) Optional Dependencies
Interface Injection ผ่าน Interface Setter Method Legacy/Plugin Systems
Service Locator ขอจาก Container เอง (Anti-pattern) หลีกเลี่ยง (Hidden Dependencies)

1. Constructor Injection (แนะนำมากที่สุด)

public class OrderService
{
    private readonly IEmailService _email;
    private readonly ILogger _logger;
    
    // Dependencies ถูก Require ตอนสร้าง Object
    public OrderService(IEmailService email, ILogger logger)
    {
        _email = email ?? throw new ArgumentNullException(nameof(email));
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
    }
    
    public void PlaceOrder(Order order)
    {
        _logger.Log("Placing order...");
        // ...
        _email.SendConfirmation(order.CustomerEmail);
    }
}

2. Setter/Property Injection

public class ReportGenerator
{
    // Optional Dependency
    public IExportService ExportService { get; set; }
    
    public void Generate()
    {
        // ใช้ได้ถ้ามี Set ไว้
        ExportService?.Export(data);
    }
}

// Usage
var generator = new ReportGenerator();
generator.ExportService = new PdfExportService(); // Optional

3. Interface Injection

public interface ILoggerInjector
{
    void InjectLogger(ILogger logger);
}

public class PaymentProcessor : ILoggerInjector
{
    private ILogger _logger;
    
    public void InjectLogger(ILogger logger) => _logger = logger;
    
    public void Process() => _logger?.Log("Processing...");
}

4. Service Locator (Anti-Pattern - หลีกเลี่ยง)

// ❌ ไม่แนะนำ - Hidden Dependencies
public class BadExample
{
    public void DoWork()
    {
        var logger = ServiceLocator.Current.GetInstance<ILogger>(); // Hidden!
        logger.Log("Working...");
    }
}

ข้อดีของ DI

  • Loose Coupling: Class ไม่รู้จัก Concrete Implementation
  • Testability: Mock Dependencies ได้ง่ายใน Unit Test
  • Reusability: ใช้ Class ซ้ำได้กับ Implementation ต่างกัน
  • Maintainability: แก้ Implementation แทนได้โดยไม่กระทบ Consumer
  • Centralized Configuration: DI Container จัดการ Lifetime/Registration ที่เดียว

ข้อเสีย/ความท้าทาย

  • Complexity เพิ่มขึ้น (Container, Registration, Lifetime)
  • Constructor Over-injection (Parameter 過多) → Refactor to Facade/Aggregate Service
  • Runtime Errors หาก Registration ผิด (Compile-time ไม่จับได้)
  • Performance Overhead น้อย (Container Resolution)

DI ใน ASP.NET Core (Built-in Container)

// Program.cs
builder.Services.AddScoped<IEmailService, SmtpEmailService>();
builder.Services.AddSingleton<ILogger, SerilogLogger>();
builder.Services.AddTransient<OrderService>(); // Concrete Type ก็ได้

// Controller ใช้ Constructor Injection
public class OrdersController : ControllerBase
{
    private readonly OrderService _orderService;
    public OrdersController(OrderService orderService) => _orderService = orderService;
}

Lifetime Options

  • Transient: New Instance ทุกครั้งที่ Request
  • Scoped: One Instance ต่อ Request (HTTP Request)
  • Singleton: One Instance ตลอด Application Lifetime