본문 바로가기
C#

C#] SOLID 원칙 5. Dependency Inversion Principle(의존성 역전 원칙)

by Fastlane 2024. 2. 7.
728x90
반응형

Dependency Inversion Principle의 기본 아이디어는 lower-level 모듈의 변경에 의해 영향이 없고 재사용성이 있는 higher-level modules를 만들어야 한다는 것이다. 

 

  • High-level 모듈은 low-level 모듈에 의존하지 않는다. 둘다 abstractions에 의존한다. 
  • Abstractions은 상세기능에 의존하지 않는다. 상세기능은 abstractions에 의존한다. 

이해하기 위해, 다음 예를 살펴보자. 

 

High-Level, Low-Level 모듈이란

high-level 모듈의 기능은 추상적이며, 복잡한 로직을 가지고 있다. 

low-leve 모듈은 application의 작은 파트와 상세내용에 집중한다. high-level 모듈안에서 사용된다. 

 

Dependency Injection은 Dependency Inversion Principle 구현의 한가지 방법이다. 

DIP 위반 예

두 enum과 model class가 있다. 

public enum Gender
{
    Male,
    Female
}

 

public enum Position
{
    Administrator,
    Manager,
    Executive
}

 

public class Employee
{
    public string Name { get; set; }
    public Gender Gender { get; set; }
    public Position Position { get; set; }
}

low-level class를 만들어보자. 

public class EmployeeManager
{
    private readonly List<Employee> _employees;

    public EmployeeManager()
    {
        _employees = new List<Employee>();
    }

    public void AddEmployee(Employee employee)
    {
        _employees.Add(employee);
    }
}

employees에 대해 통계분석을 실행하는 higher-level class를 만들어보자. 

public class EmployeeStatistics
{
    private readonly EmployeeManager _empManager;

    public EmployeeStatistics(EmployeeManager empManager)
    {
        _empManager = empManager;
    }

    public int CountFemaleManagers()
    {
        //logic goes here
    }
}

EmployeeStatistics class에서 _employees list를 사용할 수 없기 때문에, private _employees list를 노출해야 한다. 

public class EmployeeManager
{
    private readonly List<Employee> _employees;
    
    public EmployeeManager()
    {
        _employees = new List<Employee>();
    }

    public void AddEmployee(Employee employee)
    {
        _employees.Add(employee);
    }

    public List<Employee> Employees => _employees;
}

이제 Count 함수 로직을 작성할 수 있다. 

public class EmployeeStatistics 
{ 
    private readonly EmployeeManager _empManager; 
    public EmployeeStatistics(EmployeeManager empManager) 
    {
       _empManager = empManager; 
    } 

    public int CountFemaleManagers () => 
      _empManager.Employees.Count(emp => emp.Gender == Gender.Female && emp.Position == Position.Manager);
 }

잘 동작한다. 좋은 코드인 것 같지만, 이것은 DIP를 따르지 않는다. 

 

어떻게 그러한가?

 

첫번째로, EmployeeStatistics class는 EmployeeManager class와 strong coupled되어있다. 우리는 EmployeeManager object를 제외하고 EmployeeStatistics constructor에 다른 object를 보낼 수 없다. 

 

두번째로, high-level class에서 low-level class의 public property를 사용하고 있다. 이렇게되면 low-level class를 변경하기 어렵다. Employees를 list 대신에 dictionary로 변경하고 싶다면 EmployeeStatistics class쪽 소스도 수정해야 한다. 이는 우리가 피하고 싶은 것이다. 

 

DIP를 구현하므로 더 나은 코드를 만드는 방법

두 class를 abstraction에 의존하기 변경하고 싶다. 

첫번째로 IEmployeeSearchable interface를 만들어보자. 

public interface IEmployeeSearchable
{
    IEnumerable<Employee> GetEmployeesByGenderAndPosition(Gender gender, Position position);
}

EmployeeManager class를 수정하자. 

public class EmployeeManager: IEmployeeSearchable
{
    private readonly List<Employee> _employees;

    public EmployeeManager()
    {
        _employees = new List<Employee>();
    }
    
    public void AddEmployee(Employee employee)
    {
        _employees.Add(employee);
    }

    public IEnumerable<Employee> GetEmployeesByGenderAndPosition(Gender gender, Position position)
        => _employees.Where(emp => emp.Gender == gender && emp.Position == position);
}

마지막으로 EmployeeStatistics class를 수정해보자. 

public class EmployeeStatistics
{
    private readonly IEmployeeSearchable _emp;

    public EmployeeStatistics(IEmployeeSearchable emp)
    {
        _emp = emp;
    }

    public int CountFemaleManagers() => 
    _emp.GetEmployeesByGenderAndPosition(Gender.Female, Position.Manager).Count();
}

훨씬 나아 보인다. DIP 규칙도 잘 구현되었다. 

DIP 구현의 장점

모듈 간의 의존성을 줄이는 것이 중요하다. DIP를 정확하기 구현하므로 가능하다. class가 lower-tier objects와 느슨하게 결합되어 있으면, high-tier modules로부터 로직을 재사용하기 쉽다. 

 

이미 구현된 모듈의 변경은 risky하다. abstraction에 의존하므로, high-level 모듈변경 시 위험을 줄일 수 있따. 

 

application 구조 전체 수준에서 확장성과 안정성을 준다. 

728x90
반응형

댓글