의존과 DI

이 글과 하위 글들은 [객체 지향 프로그래밍 입문]에 관한 내용입니다. 최범균님의 인프런의 강의를 보며 정리한 내용으로 문제가 될시 삭제하겠습니다.

의존

기능 구현을 위해 다른 구성 요소를 사용하는 것을 의존한다고 표현한다. 의존의 예로는 객체 생성, 메서드 호출, 데이터 사용 등이 있다.

의존은 변경이 전파될 가능성을 의미하기도 한다. 즉 의존하는 대상이 바뀌면 의존 했던 부분도 바뀔 가능성이 높아지게 된다.

  • 예) 호출하는 메서드의 파라미터가 변경, 호출하는 메서드가 발생할 수 있는 익셉션 타입이 추가

순환 의존

변경 연쇄 전파 가능성이 많다. 클래스, 패키지, 모듈 등 모든 수준에서 순환 의존이 없도록해야 한다.

의존하는 대상이 많다면?

A~F까지의 내용이 바뀐다면 X도 그만큼 바뀔 확률이 커진다. 그래서 의존하는 대상은 적을수록 좋다.

  • 한 클래스에서 많은 기능을 제공하는 경우: 각 기능마다 의존하는 대상이 다를 수 있으며, 한 기능의 변경이 다른 기능에 영향을 줄 수도 있다.

class UserService {
  regist(regReq: RegReq): void {
    // ...
  }
  changePw(chgReq: ChangeReq): void {
    // ...
  }
  blockUser(id: string, reason: string): void {
    // ...
  }
}

위와 같은 경우에는 기능 별로 클래스를 분리하는 것을 고려한다. 이렇게 하면 클래스의 수는 많아지지만, 각 클래스마다 필요로하는 의존이 줄어들게 되고, 한 기능을 수정할때 다른 기능을 수정할 일이 발생하지 않게된다.

또한 개별 기능을 테스트하기도 더 수월해진다.

class UserRegistService {
  regist(...): void {
    // ...
  }
}

class ChangePwService {
  changePw(...): void {
    // ...
  }
}

class UserBlockService {
  blockUser(...): void {
    // ...
  }
}

의존 대상이 많은 경우를 해결하는 또 다른 방법은 단일 기능으로 묶을 수 있는지 검토해 보는 것이다.

물론 의존 대상 객체를 직접 생성할 수도 있겠지만, 생성 클래스가 바귀면 의존하는 코드도 바뀌게 된다. 의존하는 대상 객체를 직접 생성하지 않는 방법으로는 아래와 같은 방법이 있다.

  • 팩토리, 빌더

  • 의존 주입(Dependency Injection)

  • 서비스 로케이터(Service Locator)

의존 주입(Dependency Injection)

외부에서 의존 객체를 주입하는 방식이다. 생성자나 메서드를 이용해서 주입한다.

class ScheduleService {
  #repository: UserRepository;
  #cal: Calculator;

  constructor(repository: UserRepository) {
    this.#repository = repository;
  }

  setCalculator(cal: Calculator): void {
    this.#cal = cal;
  }
}

// 초기화 코드
const userRepo: UserRepository = new DbUserRepository();
const cal: Calculator = new Calculator();

const schSvc: ScheduleService = new ScheduleService(userRepo);
schSvc.setCalculator(cal);

DI 장점

  • 상위 타입을 사용할 경우 의존 대상이 바뀌면 조립기(설정)만 변경하면 된다. -> 스프링이 예라.. 이런 어떻게 이해해야 할지 조금 감이 안옴. 나중에 다른 자료를 더 볼 예정

  • 의존하는 객체 없이 대역 객체를 사용해서 테스트할 수 있다.

DI를 습관처럼 사용하자.

의존 객체는 주입 받도록 코드를 작성하는 습관을 들이자.

Last updated