การฉีดผ่าน Constructor และอินเทอร์เฟซ
ใช้การฉีดผ่าน constructor ร่วมกับการออกแบบที่อิงอินเทอร์เฟซ เพื่อแยกส่วนประกอบออกจากกันและรองรับการทดสอบหน่วย
การฉีดผ่าน Constructor และอินเทอร์เฟซ เป็นบทเรียน C# Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน C# Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส C# Academy มีบทเรียนทั้งหมด 4 บทเรียน
พื้นฐานการฉีดผ่านคอนสตรัคเตอร์
การฉีดผ่านคอนสตรัคเตอร์เป็นรูปแบบ DI ที่ใช้กันมากที่สุด คลาสจะประกาศสิ่งที่ต้องพึ่งพาเป็นพารามิเตอร์ของคอนสตรัคเตอร์ และคอนเทนเนอร์จะจัดเตรียมสิ่งเหล่านั้นให้เมื่อสร้างอินสแตนซ์
วิธีนี้ทำให้สิ่งที่ต้องพึ่งพา ชัดเจน จำเป็น และมองเห็นได้
การกำหนดอินเทอร์เฟซ
เริ่มต้นด้วยอินเทอร์เฟซที่กำหนดข้อตกลง วิธีนี้ทำให้สามารถสลับการนำไปใช้งานต่าง ๆ ไม่ว่าจะเป็นของจริง วัตถุจำลอง หรือแบบอยู่ในหน่วยความจำได้ โดยไม่ต้องเปลี่ยนโค้ดที่พึ่งพาอินเทอร์เฟซนั้น
public interface IProductRepository
{
Task<Product?> GetByIdAsync(int id);
Task<IEnumerable<Product>> GetAllAsync();
Task AddAsync(Product product);
}การนำอินเทอร์เฟซไปใช้งาน
การนำไปใช้งานจริงจะจัดการงานต่าง ๆ เช่น การเรียกฐานข้อมูลและคำขอ HTTP เป็นต้น ผู้ใช้บริการไม่จำเป็นต้องรู้เลยว่าได้รับการนำไปใช้งานแบบใด
public class SqlProductRepository : IProductRepository
{
private readonly AppDbContext _db;
public SqlProductRepository(AppDbContext db) => _db = db;
public async Task<Product?> GetByIdAsync(int id)
=> await _db.Products.FindAsync(id);
public async Task<IEnumerable<Product>> GetAllAsync()
=> await _db.Products.ToListAsync();
public async Task AddAsync(Product product)
{
_db.Products.Add(product);
await _db.SaveChangesAsync();
}
}การใช้งานผ่านคอนสตรัคเตอร์
ชั้นบริการรับ IProductRepository เป็นพารามิเตอร์ของคอนสตรัคเตอร์ และไม่จำเป็นต้องรู้จัก SqlProductRepository โดยรู้เพียงข้อตกลงของอินเทอร์เฟซเท่านั้น
public class ProductService
{
private readonly IProductRepository _repo;
private readonly ILogger<ProductService> _logger;
public ProductService(
IProductRepository repo,
ILogger<ProductService> logger)
{
_repo = repo;
_logger = logger;
}
public async Task<Product?> GetProductAsync(int id)
{
_logger.LogInformation("Fetching product {Id}", id);
return await _repo.GetByIdAsync(id);
}
}การลงทะเบียนสายโซ่
ลงทะเบียนบริการทุกตัวในสายโซ่ คอนเทนเนอร์จะแก้ไขกราฟการพึ่งพาทั้งหมดโดยอัตโนมัติ ตั้งแต่ ProductService → IProductRepository → AppDbContext
builder.Services.AddDbContext<AppDbContext>(opt =>
opt.UseSqlite("Data Source=app.db"));
builder.Services.AddScoped<IProductRepository, SqlProductRepository>();
builder.Services.AddScoped<ProductService>();การทดสอบด้วยวัตถุจำลอง
เนื่องจากสิ่งที่ต้องพึ่งพาถูกฉีดผ่านอินเทอร์เฟซ การทดสอบหน่วยจึงสามารถส่งวัตถุจำลองหรือวัตถุปลอมเข้าไปได้โดยไม่ต้องแตะฐานข้อมูล นี่คือหนึ่งในประโยชน์ที่สำคัญที่สุดของ DI
// Using NSubstitute or Moq in a unit test
var mockRepo = Substitute.For<IProductRepository>();
mockRepo.GetByIdAsync(1).Returns(new Product { Id = 1, Name = "Widget" });
var logger = Substitute.For<ILogger<ProductService>>();
var svc = new ProductService(mockRepo, logger);
var result = await svc.GetProductAsync(1);
Assert.Equal("Widget", result?.Name);พารามิเตอร์คอนสตรัคเตอร์หลายรายการ
คลาสหนึ่งสามารถมีพารามิเตอร์คอนสตรัคเตอร์ได้หลายรายการ คอนเทนเนอร์จะแก้ไขพารามิเตอร์ทั้งหมดให้ตราบใดที่แต่ละรายการลงทะเบียนไว้แล้ว ควรทำให้คอนสตรัคเตอร์มีหน้าที่ชัดเจน เพราะพารามิเตอร์ที่มากเกินไปเป็นสัญญาณว่าคลาสมีความรับผิดชอบมากเกินไป
public class OrderService
{
public OrderService(
IOrderRepository orders,
IProductRepository products,
IEmailSender email,
ILogger<OrderService> logger)
{
// all injected by DI container
}
}การหลีกเลี่ยงรูปแบบต่อต้าน Service Locator
NOT ควรฉีด IServiceProvider แล้วเรียก GetService ภายในเมธอดทางธุรกิจ เพราะจะซ่อนสิ่งที่ต้องพึ่งพาและทำให้การทดสอบทำได้ยาก ควรใช้การฉีดผ่านคอนสตรัคเตอร์เพื่อให้โค้ดชัดเจนและทดสอบได้
// BAD
public void Process(IServiceProvider sp)
{
var repo = sp.GetService<IProductRepository>(); // hidden dep!
}
// GOOD
public class Processor
{
private readonly IProductRepository _repo;
public Processor(IProductRepository repo) => _repo = repo;
}ไวยากรณ์คอนสตรัคเตอร์หลัก (C# 12)
C# 12 เพิ่ม คอนสตรัคเตอร์หลัก สำหรับคลาสทุกชนิด ทำให้คุณประกาศพารามิเตอร์โดยตรงในการประกาศคลาส เพื่อเขียนโค้ด DI ให้กระชับขึ้น
// C# 12 primary constructor
public class ProductService(
IProductRepository repo,
ILogger<ProductService> logger)
{
public async Task<Product?> GetAsync(int id)
{
logger.LogInformation("Getting {Id}", id);
return await repo.GetByIdAsync(id);
}
}การสลับการนำไปใช้งาน
ข้อดีสำคัญของ DI ที่อิงอินเทอร์เฟซคือ คุณสามารถสลับการนำไปใช้งานได้ในบรรทัดเดียว โดยไม่ต้องเปลี่ยนโค้ดที่เรียกใช้งาน เหมาะสำหรับการสลับฐานข้อมูล ผู้ให้บริการส่งอีเมล หรือสวิตช์ฟีเจอร์
// Switch from SQL to in-memory for integration tests
if (environment.IsEnvironment("Test"))
builder.Services.AddScoped<IProductRepository, InMemoryProductRepository>();
else
builder.Services.AddScoped<IProductRepository, SqlProductRepository>();ตัวอย่างจริง: Controller กับ DI
Controller ของ ASP.NET Core รองรับการฉีดผ่านคอนสตรัคเตอร์โดยตรง เฟรมเวิร์กจะแก้ไขพารามิเตอร์ทั้งหมดแล้วส่งต่อให้
[ApiController]
[Route("api/products")]
public class ProductsController : ControllerBase
{
private readonly ProductService _svc;
public ProductsController(ProductService svc) => _svc = svc;
[HttpGet("{id}")]
public async Task<IActionResult> Get(int id)
{
var product = await _svc.GetProductAsync(id);
return product is null ? NotFound() : Ok(product);
}
}ตรวจสอบความเข้าใจ
ประโยชน์หลักของการเขียนโปรแกรมโดยยึดอินเทอร์เฟซเมื่อใช้ DI คืออะไร
ทบทวน: การฉีดผ่านคอนสตรัคเตอร์และอินเทอร์เฟซ
ประเด็นสำคัญ:
- ประกาศสิ่งที่ต้องพึ่งพาเป็นพารามิเตอร์ของคอนสตรัคเตอร์ แล้วคอนเทนเนอร์จะจัดเตรียมให้
- กำหนดอินเทอร์เฟซสำหรับข้อตกลงของบริการ และฉีดอินเทอร์เฟซแทนชนิดจริง
- อินเทอร์เฟซช่วยให้สร้างวัตถุจำลองในการทดสอบหน่วยและสลับการนำไปใช้งานได้ง่าย
- คอนสตรัคเตอร์หลักใน C# 12 ลดโค้ดซ้ำในคลาสที่ใช้ DI อย่างมาก
- หลีกเลี่ยงรูปแบบต่อต้าน Service Locator และทำให้สิ่งที่ต้องพึ่งพาชัดเจน
เรียนรู้ C# ด้วย AI tutor — ฟรี
เขียนและเรียกใช้โค้ดจริงในเบราว์เซอร์ของคุณ รับความช่วยเหลือทันทีจาก AI tutor 24/7 และเรียนรู้ต่อจากที่คุณหยุดบนเว็บหรือในแอป
- คอร์ส
- 93
- บทเรียน
- 346
คำถามที่พบบ่อย
บทเรียน “การฉีดผ่าน Constructor และอินเทอร์เฟซ” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “การฉีดผ่าน Constructor และอินเทอร์เฟซ” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส C# Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส C# Academy มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “การฉีดผ่าน Constructor และอินเทอร์เฟซ”
ใช้การฉีดผ่าน constructor ร่วมกับการออกแบบที่อิงอินเทอร์เฟซ เพื่อแยกส่วนประกอบออกจากกันและรองรับการทดสอบหน่วย คุณปฏิบัติ C# Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน C# Academy หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน C# Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน
บทเรียน “การฉีดผ่าน Constructor และอินเทอร์เฟซ” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน C# Academy นี้ได้ไหม
ได้ บทเรียน C# Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- พื้นฐานคอนเทนเนอร์ DI
- อายุการใช้งานบริการ: ชั่วคราว มีขอบเขต Singleton
- การฉีดผ่าน Constructor และอินเทอร์เฟซ
- รูปแบบ Factory และ Options