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 구조 전체 수준에서 확장성과 안정성을 준다.
'C#' 카테고리의 다른 글
C#] IDisposal Interface 구현 방법 (0) | 2024.02.14 |
---|---|
C#] Managed vs Unmanaged Code (0) | 2024.02.14 |
C#] SOLID 원칙 4. Interface Segregation Principle(인터페이스 분리 원칙) (1) | 2024.02.07 |
C#] Access Modifiers (0) | 2024.02.02 |
C#] Virtual VS Abstract 함수 차이점 (2) | 2024.02.02 |
댓글