[DI] 의존성 관리하기 (2)

728x90
반응형

객체 생성에 대한 제안

인터페이스를 기초로 한 코딩

가장 중요한 변경은 SecurityService 클래스의 실제 구현을 인터페이스 뒤로 숨기는 것이다.

이렇게 하면, AccountController 클래스가 SecurityService 클래스의 실제 구현체가 아닌 인터페이스에만 의존하게 만들 수 있다. 

 

▶ 인터페이스 추출

public interface ISecurityService
{
    void ChangeUsersPassword(string userID, string newPassword);
}

// ...
public class SecurityService : ISecurityService
{
    public void ChangeUsersPassword(string userID, string newPassword)
    {
    	// ...
    }
}

 

public class AccountController
{
    private readonly ISecurityService securityService;
    public AccountController()
    {
        this.securityService = new SecurityService();
    }
    
    
    [HttpPost]
    public void ChangePassword(string userID, string newPassword)
    {
        securityService.ChangeUserPassword(userID, newPassword);
    }
}

위 예제는 아직도 생성자에서 SecurityServer 클래스를 호출함으로 완벽하지 않다. 아직도 의존성은 존재한다.

이 두 클래스를 완전히 분리하기 위해서는 더 리팩토링이 필요하다.

 

이 시점에서 의존성 주입 ( DI, Dependency Injection )을 알아보자.

 

의존성 주입 기법 활용하기

AccountController 클래스는 SecurityService 클래스의 인스턴스를 직접 생성하는 대신, 다른 클래스에게 ISecurityService 인터페이스를 구현한 객체를 제공해줄것을 요구한다.

그리고 null 값을 전달하는 경우를 방지하기 위한 조건 역시 포함한다.

이렇게 함으로써, ChangePassword 메서드에서 securityService 필드를 참조할 때, 이 필드가 항상 유효한 인스턴스를 참조하도록 보장할 수 있어, 그 어디에서도 null 값 검사를 수행할 필요가 없게 된다.

 

public interface ISecurityService
{
    void ChangeUsersPassword(string userID, string newPassword);
}



public class SecurityService : ISecurityService
{
    private readonly IUserRepository userRepository;
    public SecurityService(IUserRepository userRepository)
    {
        if(userRepository == null) throw new ArgumentNullException("userRepository");
        this.userRepository = userRepository;
    }
    
    public void ChangeUsersPassword(string userID, string newPassword)
    {
    	var user = userRepository.GetByID(userID);
        user.ChangePassword(newPassword);
    }
}





public class AccountController
{
    private readonly ISecurityService securityService;
    public AccountController(ISecurityService securityService)
    {
        if(securityService == null) throw new ArgumentNullException("securityService");
        this.securityService = securityService;
    }
    
    
    [HttpPost]
    public void ChangePassword(string userID, string newPassword)
    {
        securityService.ChangeUserPassword(userID, newPassword);
    }
}

AccountController 클래스가 유효한 ISecurityService 인터페이스의 인스턴스를 전달하기를 강요하는 것 처럼 SecurityService 클래스 또한 유효한 IUserRepository 인터페이스의 인스턴스를 전달할 것을 강요한다.

마찬가지로, UserRepository 클래스에 대한 의존성 역시 IUserRepository 인터페이스에 의해 완전히 제거되었다.

 

 

해당 글은 C#으로 배우는 적응형 코드 서적을 토대로 개념을 정리중인 글입니다.
728x90