Wstrzykiwanie przez konstruktor i interfejsy
Stosuj wstrzykiwanie przez konstruktor oraz projektowanie oparte na interfejsach, aby rozdzielać komponenty i umożliwiać testy jednostkowe.
Wstrzykiwanie przez konstruktor i interfejsy to bezpłatna lekcja C# Academy na CoddyKit. To lekcja 3 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej C# Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs C# Academy zawiera 4 lekcji w sumie.
Podstawy wstrzykiwania przez konstruktor
Wstrzykiwanie przez konstruktor jest najczęściej używanym wzorcem DI. Klasa deklaruje swoje zależności jako parametry konstruktora, a kontener dostarcza je podczas tworzenia instancji.
Dzięki temu zależności są jawne, wymagane i widoczne.
Definiowanie interfejsu
Należy zacząć od interfejsu definiującego kontrakt. Pozwala to wymieniać różne implementacje — rzeczywiste, mocki lub działające w pamięci — bez zmieniania kodu zależnego.
public interface IProductRepository
{
Task<Product?> GetByIdAsync(int id);
Task<IEnumerable<Product>> GetAllAsync();
Task AddAsync(Product product);
}Implementowanie interfejsu
Konkretna implementacja wykonuje właściwą pracę — wywołania bazy danych, żądania HTTP itp. Kod korzystający z usługi nie musi wiedzieć, którą implementację otrzymuje.
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();
}
}Korzystanie za pomocą konstruktora
Warstwa usług przyjmuje IProductRepository jako parametr konstruktora. Nie zna ona SqlProductRepository — zna jedynie kontrakt interfejsu.
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);
}
}Rejestrowanie całego łańcucha
Należy zarejestrować każdą usługę w łańcuchu. Kontener automatycznie rozwiąże cały graf zależności — ProductService → IProductRepository → AppDbContext.
builder.Services.AddDbContext<AppDbContext>(opt =>
opt.UseSqlite("Data Source=app.db"));
builder.Services.AddScoped<IProductRepository, SqlProductRepository>();
builder.Services.AddScoped<ProductService>();Testowanie za pomocą mocka
Ponieważ zależności są wstrzykiwane za pośrednictwem interfejsów, testy jednostkowe mogą przekazywać mock lub atrapę bez uzyskiwania dostępu do bazy danych. To jedna z największych korzyści 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);Wiele parametrów konstruktora
Klasa może mieć wiele parametrów konstruktora. Kontener rozwiąże je wszystkie, o ile każdy z nich został zarejestrowany. Należy zachować zwięzłość konstruktorów — zbyt wiele parametrów sugeruje, że klasa ma zbyt wiele odpowiedzialności.
public class OrderService
{
public OrderService(
IOrderRepository orders,
IProductRepository products,
IEmailSender email,
ILogger<OrderService> logger)
{
// all injected by DI container
}
}Unikanie antywzorca Service Locator
NIE należy wstrzykiwać IServiceProvider i wywoływać GetService wewnątrz metod biznesowych. Ukrywa to zależności i utrudnia testowanie. Należy preferować wstrzykiwanie przez konstruktor, aby kod był jawny i łatwy do testowania.
// 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;
}Składnia konstruktora podstawowego (C# 12)
C# 12 wprowadza konstruktory podstawowe dla wszystkich typów klas, umożliwiając deklarowanie parametrów bezpośrednio w deklaracji klasy, co pozwala zwięźlej zapisywać kod 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);
}
}Wymiana implementacji
Kluczową zaletą DI opartego na interfejsach jest możliwość wymiany implementacji w jednej linii bez zmieniania kodu, który z niej korzysta. Jest to przydatne przy zmianie baz danych, dostawców poczty lub flag funkcji.
// Switch from SQL to in-memory for integration tests
if (environment.IsEnvironment("Test"))
builder.Services.AddScoped<IProductRepository, InMemoryProductRepository>();
else
builder.Services.AddScoped<IProductRepository, SqlProductRepository>();W praktyce: kontroler z DI
Kontrolery ASP.NET Core natywnie korzystają ze wstrzykiwania przez konstruktor. Platforma rozwiązuje wszystkie parametry i przekazuje je do kontrolera.
[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);
}
}Szybkie sprawdzenie
Jaka jest główna korzyść z programowania z użyciem interfejsów podczas korzystania z DI?
Podsumowanie: wstrzykiwanie przez konstruktor i interfejsy
Najważniejsze informacje:
- Należy deklarować zależności jako parametry konstruktora — kontener je dostarczy
- Należy definiować interfejsy dla kontraktów usług i wstrzykiwać interfejs, a nie typ konkretny
- Interfejsy ułatwiają tworzenie mocków w testach jednostkowych oraz wymianę implementacji
- Konstruktory podstawowe w C# 12 ograniczają ilość kodu powtarzalnego w klasach intensywnie korzystających z DI
- Należy unikać antywzorca Service Locator — zależności powinny pozostać jawne
Często zadawane pytania
Czy lekcja „Wstrzykiwanie przez konstruktor i interfejsy” jest bezpłatna?
Tak — pełny tekst „Wstrzykiwanie przez konstruktor i interfejsy” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu C# Academy, przejdź na CoddyKit PRO. Kurs C# Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „Wstrzykiwanie przez konstruktor i interfejsy”?
Stosuj wstrzykiwanie przez konstruktor oraz projektowanie oparte na interfejsach, aby rozdzielać komponenty i umożliwiać testy jednostkowe. Ćwiczysz C# Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć C# Academy?
Nie wymagamy żadnego doświadczenia. C# Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 4.
Ile czasu zajmuje lekcja „Wstrzykiwanie przez konstruktor i interfejsy”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji C# Academy?
Tak. Każda lekcja C# Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Podstawy kontenera DI
- Czas życia usług: Transient, Scoped, Singleton
- Wstrzykiwanie przez konstruktor i interfejsy
- Wzorce Factory i Options