본문 바로가기

IT/개발상식

의존성 주입

안녕하세요. FoxMon이에요. 다들 건강히 잘 지내고 계시지요? 저도 잘 지내고 있답니다. 요즘 날씨가 따뜻하기도 했다가, 추워지기도 했다가 하네요. 최근엔 눈도 정말 많이 왔었지요. 저는 개인적으로 추운 날씨보단 더운 날씨를 좋아한답니다. 푸릇푸릇한 계절을 참 좋아하기 때문이죠. 추우면 입어야 할 옷도 참 많아지고 괜스레 거슬리기도 하고 불편하더라구요.

안궁금하다구요? 좀 궁금해 주세요

아무튼 서론이 길었네요. 알겠어요. 바로 본론으로 들어가 볼까요?

오늘은 의존성 주입에 대해 알아보죠. 의존성이란 무엇일까요? 제가 앞서 말씀드린 글에서 살펴 보자면 저희의 옷은 날씨에 대해서 영향을 받기도 하죠. 날이 더우면 입어야 하는 옷은 반팔이 될거구요. 날이 춥다면 긴팔이나 혹은 패딩과 같은 겉옷을 입기도 하겠네요. 이렇듯 옷은 저희가 선택을 할 수도 있지만 외부에서 주입받은 어떠한 이유에 의해서 결정이 되기도 하죠? 이러한 느낌적인 느낌을 보통 의존성 주입이라고 표현하기도 한답니다. 이게 왜 필요한지 함께 살펴보도록 할게요.




의존성 주입

간단하게 자동차를 예시로 들어볼게요. 정말 단순하게 생각해보면 자동차는 저마다의 이름과 속도를 가지고 있죠. 그리고 움직이기도 하고, 멈추기도 할것이죠. 저는 이러한 기본적인 자동차의 모양을 추상 클래스로 정의해 볼게요. 아래처럼요.

abstract class BaseCar {
  private name: string;

  protected speed: number;

  constructor(name: string) {
    this.name = name;

    this.speed = 0;
  }

  protected abstract move(): number;

  protected abstract stop(): void;
}

abstract란 앞서 말씀드린 것처럼 단순히 추상화 단계에 걸쳐 제가 생각한 class의 모양이에요. 즉 추상 클래스를 상속받은 클래스는 추상화된 것들을 구체화 시켜주기도 해야 하죠. 저는 이러한 BaseCar를 빠른 자동차와, 느린 자동차로 확장해 볼 것입니다.

class SpeedCar extends BaseCar {
  constructor() {
    super("스피드 자동차");
  }

  move(): number {
    this.speed += 10;

    return this.speed;
  }

  stop(): void {
    this.speed = 0;
  }
}

class SlowCar extends BaseCar {
  constructor() {
    super("느린 자동차");
  }

  move(): number {
    this.speed += 1;

    return this.speed;
  }

  stop(): void {
    this.speed = 0;
  }
}

이렇게 작성한다면 SlowCar와 SpeedCar는 추상 클래스에서 가지고 있는 행위들을 자신만의 다른 색깔로 재정의(오버라이딩)하게 되는 것이죠. 즉 같은 자동차지만 다른 자동차가 되는 것입니다. 하지만 그 기원은 같지요. 그럼 이렇게 확장시킨 클래스들을 어디서 어떻게 사용할 수 있을까요? 그리고 왜 이렇게 해야 할까요?

경우에 따라서는 SlowCar가 필요할 수도 있구요. 혹은 SpeedCar가 필요할 수도 있습니다. 예시로 한 번 볼게요.

class CarGroup {
  car: BaseCar;

  constructor(car: BaseCar) {
    this.car = car;
  }
}

new CarGroup(new SpeedCar());

CarGroup이 자동차를 생성해 주는 class라면 요구사항에 맞게 그때그때 필요한 Car들을 내놓을 수 있어야 합니다. 그리고 특히 주의깊게 보셔야 할 것은 constructor부분의 파라미터 car에 대한 type입니다. 사실 SlowCar나 SpeedCar 모두 그 기원은 BaseCar이죠. 따라서 둘의 type은 BaseCar가 될 수 있는 것이기도 하죠. 즉 CarGroup이라는 녀석은 외부 환경에 따라서 그때그때 필요한 자동차를 외부에서 선택하여 주입할 수 있게 더 넓은 선택지를 제공하죠. 이 부분은 특히 중요한데요. 한 번 차이점을 볼까요?

만약 CarGroup안에 미리 car의 type이 지정돼 있다면 어떨까요?

class CarGroup {
    car: SlowCar

    constructor() {
        this.car = new SlowCar()
    }
}

사실 위의 코드만 봐도 벌써 체감이 되실거에요. 여기서 만약 경우에 따라서 SlowCar가 아닌 SpeedCar가 필요한 상황이라면 어떨까요? 위의 코드를 약간 수정해야 합니다.

class CarGroup {
    car: SlowCar | SpeedCar

    constructor(type: string) {
        switch(type) {
            case "Speed":
                this.car = new SpeedCar();
                break;

            case "Slow":
                this.car = new SlowCar();
                break;
        }
    }
}

확실히 처음의 CarGroup보다는 확장성이 떨어지지 않나요? 뭔가 지속적으로 늘어날때마다 손봐야 하는 부분이 점점 늘어나는 느낌입니다. 확장성 부분에서 확실히 불리하다고 생각이 되시지 않나요? 또한 외부에서 내가 필요한 시점에 내가 원하는 의존성을 주입하는 것이 코드에서 하고자 하는 의도가 명확하게 전달되는 느낌도 듭니다.




의의

저는 주로 코드를 작성할 때 지금 당장의 상황만 보고 작성하는 버릇은 최대한 줄이려고 합니다. 사실 시스템이 오래될수록 기능 요구사항은 점점 많아질 것이고, 그 때 쉽게 작성하기 위해선 확장성 좋은 코드로 작성되어 있는 것이 오류가 날 가능성도 적고 실수 또한 줄일 수 있거든요. 그런 의미로 본다면 의존성 주입은 꽤나 중요한 개념인 것 같다고 생각이 들어요.

간략하게 오늘, 의존성 주입에 대해 알아 보았는데요. 오늘도 역시 급하게 글을 마무리 하도록 하겠습니다.

안녕히 계세요~~!

'IT > 개발상식' 카테고리의 다른 글

[개발상식] 라이브러리와 프레임워크에 관한 건  (0) 2024.04.06
프론트엔드, 백엔드  (0) 2024.03.26
Class  (0) 2024.02.06
추상화  (0) 2024.01.10
Clean Code  (0) 2023.12.02