의존성 주입(DI)이란?
**의존성 주입(Dependency Injection)**은 인터페이스와 DI 컨테이너를 활용해 MVC 응용프로그램 내부의 구성 요소들을 서로 분리시키는 설계 방식이다. DI 컨테이너는 다음과 같은 역할을 수행한다:
- 인터페이스 구현 인스턴스 생성: 객체가 의존하는 인터페이스 구현체의 인스턴스를 생성한다.
- 생성자 주입: 생성된 인스턴스를 의존하는 객체의 생성자에 주입한다.
- 결합도 감소: 클래스 간의 강한 결합을 제거하고 느슨한 결합을 구현한다.
DI를 도입하지 않은 경우: 강한 결합
DI를 도입하지 않으면 클래스 간의 결합도가 높아지고, 재사용성과 확장성이 떨어진다. 아래는 DI 없이 강하게 결합된 구조의 예제다.
Product.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace EssentialTools.Models
{
public class Product
{
public int ProductID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
public string Category { get; set; }
}
}
LinqValueCalculator.cs
namespace EssentialTools.Models
{
public class LinqValueCalculator
{
public decimal ValueProducts(IEnumerable<Product> products) {
return products.Sum(p => p.Price);
}
}
}
ShoppingCart.cs
namespace EssentialTools.Models
{
public class ShoppingCart
{
private LinqValueCalculator calc;
public ShoppingCart(LinqValueCalculator calc)
{
this.calc = calc;
}
public IEnumerable<Product> Products { get; set; }
public decimal CalculateProductTotal() {
return calc.ValueProducts(Products);
}
}
}
=> ShoppingCart 클래스는 LinqValueCalculator와 강하게 결합되어 있다.
HomeController.cs
namespace EssentialTools.Controllers
{
public class HomeController : Controller
{
private Product[] products = {
new Product { Name="kayak", Category="WaterSports", Price=275M},
new Product { Name="Lifejacket", Category="WaterSports", Price=48.95M},
new Product { Name="Soccer ball", Category="Soccer", Price=19.50M},
new Product { Name="Corner flag", Category="Soccer", Price=34.95M},
};
// GET: Home
public ActionResult Index()
{
LinqValueCalculator calc = new LinqValueCalculator();
ShoppingCart cart = new ShoppingCart(calc) { Products = products };
decimal totalValue = cart.CalculateProductTotal();
return View(totalValue);
}
}
}
=> HomeController 클래스는 ShoppingCart와 LinqValueCalculator에 강하게 결합되어 있다.
문제점
- 강한 결합: HomeController는 ShoppingCart와 LinqValueCalculator에 직접적으로 의존한다.
- 테스트 어려움: LinqValueCalculator를 대체하거나 테스트용 Mock 객체로 교체하기 어렵다.
- 유연성 부족: LinqValueCalculator의 구현을 수정하거나 교체하려면 모든 의존성을 수동으로 수정해야 한다.
Ninject 사용하기
인터페이스로 의존성 분리
먼저 인터페이스를 도입하여 계산 로직의 정의와 구현을 분리한다. 이를 통해 구현체를 교체하거나 확장할 수 있는 유연성을 확보할 수 있다.
IValueCalculator 인터페이스
계산 로직의 정의를 인터페이스로 추상화한다.
namespace EssentialTools.Models {
public interface IValueCalculator {
decimal ValueProducts(IEnumerable<Product> products);
}
}
LinqValueCalculator 구현
인터페이스를 구현한 실제 클래스이다.
namespace EssentialTools.Models {
public class LinqValueCalculator : IValueCalculator {
public decimal ValueProducts(IEnumerable<Product> products) {
return products.Sum(p => p.Price);
}
}
}
ShoppingCart 클래스
IValueCalculator 인터페이스를 주입받아 계산 작업을 수행한다.
namespace EssentialTools.Models {
public class ShoppingCart {
private IValueCalculator calc;
public ShoppingCart(IValueCalculator calc) {
this.calc = calc;
}
public IEnumerable<Product> Products { get; set; }
public decimal CalculateProductTotal() {
return calc.ValueProducts(Products);
}
}
}
문제점
ShoppingCart 클래스는 인터페이스를 사용하므로 결합도가 낮지만, 컨트롤러에서 여전히 구현체를 직접 생성해야 한다.
IValueCalculator calc = new LinqValueCalculator();
컨트롤러가 구체적인 구현 클래스에 의존하기 때문에 유연성이 떨어진다.
Ninject로 의존성 주입 구현
Ninject 커널 설정
Ninject를 사용하면 의존성을 관리하는 코드를 컨트롤러에서 제거할 수 있다. 아래는 Ninject 커널을 설정하여 IValueCalculator와 LinqValueCalculator 간의 관계를 정의하는 코드이다.
NuGet 패키지 설치
먼저 Ninject 패키지를 설치한다.
Install-Package Ninject -Version 3.0.1.10
Install-Package Ninject.Web.Common -Version 3.0.0.7
Install-Package Ninject.MVC3 -Version 3.0.0.6
커널 생성 및 객체 생성
public ActionResult Index() {
IKernel ninjectKernel = new StandardKernel();
ninjectKernel.Bind<IValueCalculator>().To<LinqValueCalculator>();
IValueCalculator calc = ninjectKernel.Get<IValueCalculator>();
ShoppingCart cart = new ShoppingCart(calc) {
Products = products
};
decimal totalValue = cart.CalculateProductTotal();
return View(totalValue);
}
설명
- 커널 생성: IKernel ninjectKernel = new StandardKernel();
- Ninject 커널은 의존성 관리를 위한 핵심 엔진이다.
- 의존성 바인딩: ninjectKernel.Bind<IValueCalculator>().To<LinqValueCalculator>();
- 인터페이스와 구현 클래스 간의 관계를 설정한다.
- 객체 생성: ninjectKernel.Get<IValueCalculator>();
- Ninject가 IValueCalculator의 구현체(LinqValueCalculator)를 생성한다.
MVC에 의존성 해결자 등록
MVC 응용프로그램이 Ninject를 사용하도록 설정하려면 **의존성 해결자(Dependency Resolver)**를 구현해야 한다.
Ninject 의존성 해결자
namespace EssentialTools.Infrastructure {
public class NinjectDependencyResolver : IDependencyResolver {
private IKernel kernel;
public NinjectDependencyResolver(IKernel kernel) {
this.kernel = kernel;
AddBindings();
}
public object GetService(Type serviceType) {
return kernel.TryGet(serviceType);
}
public IEnumerable<object> GetServices(Type serviceType) {
return kernel.GetAll(serviceType);
}
private void AddBindings() {
kernel.Bind<IValueCalculator>().To<LinqValueCalculator>();
}
}
}
- AddBindings 메서드: 인터페이스와 구현 클래스 간의 관계를 정의한다.
- GetService 및 GetServices 메서드: MVC가 의존성을 해결할 때 호출하는 메서드로, Ninject 커널을 통해 객체를 생성한다.
App_Start 폴더에 생성된 NinjectWebCommon.cs 파일에 다음 코드를 추가한다.
private static void RegisterServices(IKernel kernel) {
System.Web.Mvc.DependencyResolver.SetResolver(
new EssentialTools.Infrastructure.NinjectDependencyResolver(kernel)
);
}
HomeController 리팩토링
컨트롤러가 의존성을 선언만 하고, Ninject가 생성자 주입을 통해 구현체를 전달하도록 변경한다.
namespace EssentialTools.Controllers {
public class HomeController : Controller {
private IValueCalculator calc;
private Product[] products = {
new Product { Name = "Kayak", Category = "WaterSports", Price = 275M },
new Product { Name = "Lifejacket", Category = "WaterSports", Price = 48.95M },
new Product { Name = "Soccer ball", Category = "Soccer", Price = 19.50M },
new Product { Name = "Corner flag", Category = "Soccer", Price = 34.95M },
};
public HomeController(IValueCalculator calcParam) {
calc = calcParam;
}
public ActionResult Index() {
ShoppingCart cart = new ShoppingCart(calc) { Products = products };
decimal totalValue = cart.CalculateProductTotal();
return View(totalValue);
}
}
}
의존성 주입의 효과
- 클래스 간 결합도 감소: HomeController는 IValueCalculator에만 의존하며, 구체적인 구현체를 알 필요가 없다.
- 테스트 용이성: IValueCalculator의 Mock 객체를 쉽게 주입하여 단위 테스트를 수행할 수 있다.
- 확장성 향상: 새로운 계산 로직이 필요할 때 인터페이스를 구현한 새로운 클래스를 작성하고, 바인딩만 변경하면 된다.
Ninject는 MVC의 DI를 간단하고 직관적으로 구현할 수 있는 강력한 도구로, DI를 통해 유연하고 테스트 가능한 코드를 작성할 수 있다.
'웹 개발 > ASP .NET' 카테고리의 다른 글
[ASP.NET MVC5] 람다식을 활용한 필터링과 간결한 코드 작성 (1) | 2024.11.26 |
---|---|
[ASP.NET MVC5] 확장 메서드 (0) | 2024.11.25 |
[ASP.NET MVC5] 자동 구현 속성 (0) | 2024.11.25 |
[ASP.NET MVC5] MVC 패턴과 샘플 프로젝트 (0) | 2024.11.25 |
[ASP.NET MVC5] 동적 출력 (0) | 2024.11.25 |