본문 바로가기

[OOP] 의존과 DI (객체 지향과 디자인 패턴)

 

들어가며

스타트업의 개발자로서 좋은 설계가 우선인가, 아니면 빠른 기능 개발이 우선인가 항상 고민하곤 했습니다. 제가 다녔던 스타트업은 빠르게 기능 개발을 해서, 시장에서 인정받아야 했기에, 빠른 시간 안에 많은 기능을 개발해야 했습니다. 그러다 문득 추가 기능을 개발하거나, 기존 기능을 수정해야 할 때, 많은 어려움을 겪곤 했습니다. 그렇게 일을 마치고, 더 나은 개발자가 되기 위한 공부를 하면서 제가 작성한 코드는 배려가 부족한 코드였다는 것을 깨달았습니다.

 

제가 개발한 코드는 당장의 기능 개발은 빠르게 할 수 있더라도, 재사용성이 대단히 떨어졌고, 가독성 또한 대단히 떨어졌습니다. 또한 기능을 수정하거나 추가할 때도 광범위한 코드를 건드려야만 했기에 효율성도 떨어졌습니다. 빠르게 기능을 개발하기 위해 적었던 코드가 언젠가는 발목을 잡을 수 있는 코드가 될 수 있다는 것을 깨달았습니다. 그렇다면 비즈니스에 도움이 되고, 다른 팀원들에게도 도움이 될 수 있는, 배려가 담긴 코드를 작성하기 위해서는 어떻게 해야 할까 고민하기 시작했습니다. 배려가 담긴 코드를 작성하기 위해 공부한 내용에 대해 작성해보고자 합니다. 이 글은 도서 객체 지향과 디자인 패턴과 의존관계 주입(Dependency Injection) 쉽게 이해하기 글을 바탕으로 작성됐습니다.

 

 

 

 

 


 

 

 

 

 

 

 

 

의존과 DI

DI는 Dependency Injection의 줄임말로, 의존관계 주입이라고 말합니다. 여기서 의존관계란 무엇일까요? 이에 대해 알아보겠습니다.

 

 

 

의존관계?

기능 구현을 위해 다른 구성 요소를 사용하는 것을 의존한다고 합니다. A가 B를 의존한다고 했을 때, 만약 B의 기능이 추가 혹은 변경됐을 때, 그 영향이 A에 미친다는 것입니다. 객체를 생성하거나 메서드를 호출하거나 데이터를 사용하는 것이 모두 의존에 해당합니다.

 

 

 

 

출처: 객체 지향 프로그래밍 입문 - 최범균

 

 

 

위의 예시는 A가 B에 의존하고, B는 C에 의존하고 있습니다. 그리고 C는 다시 A에 의존합니다. 이때 C가 변경되면, C를 의존하고 있던 B에 영향이 가고, B에 영향이 가면 A에도 영향이 갈 수 있습니다. 그리고 A에 영향이 가면 다시 C에 영향이 가는 악순환이 생길 수 있습니다. 그래서 순환 의존은 대단히 위험합니다. 순환 의존은 변경의 영향이 서로 영향을 줄 가능성을 높이기 때문에 클래스, 패키지, 혹은 모듈, 모든 수준에서 순환 의존이 발생하지 않도록 해야 합니다.

 

 

 

출처: 객체 지향 프로그래밍 입문 - 최범균

 

또한 위의 예시에서 X는 A, B, C, D, E, F에 의존하고 있습니다. 만약 A가 변경된다면, A를 의존하고 있는 X가 변경될 수 있고, F가 변경되어도 X가 변경될 수 있습니다. 즉 의존하는 대상은 적을수록 좋습니다. 의존하는 대상이 적어야 변경의 가능성이 줄어들게 됩니다. 그럼 본격적으로 햄버거 가게 요리사 예시를 보며 의존관계와 DI에 대해 살펴보겠습니다.

 

 

 

의존관계 예시

예를 들어 다음의 햄버거 가게 요리사 예시를 보며 설명해보겠습니다. 햄버거 가게 요리사는 햄버거 레시피에 의존합니다. 햄버거 레시피가 변화하게 되었을 때, 변화된 레시피에 따라서 요리사는 햄버거 만드는 방법을 수정해야 합니다. 레시피의 변화가 요리사의 행위에 영향을 미쳤기 때문에, 요리사는 레시피에 의존한다고 말할 수 있습니다. 코드로 표현해보면 다음과 같습니다.

 

 

 

class BurgerChef {
  private hamBurgerRecipe: HamBurgerRecipe;

  public constructor() {
    this.hamBurgerRecipe = new HamBurgerRecipe();
  }
}

 

 

BurgerChef 클래스를 보면, 지금 구현에서는 HamBurgerRecipe만을 의존할 수 있는 구조로 되어 있습니다. 더 다양한 BurgerRecipe를 의존받을 수 있게 구현하려면 인터페이스로 추상화해야 합니다.

 

 

class BurgerChef {
  private burgerRecipe: BurgerRecipe;

  public constructor() {
    this.burgerRecipe = new HamBurgerRecipe();
    // this.burgerRecipe = new CheeseBurgerRecipe();
    // this.burgerRecipe = new ChickenBurgerRecipe();
  }
}

interface BurgerRecipe {
  newBurger(): any;
}

class HamBurgerRecipe implements BurgerRecipe {
  public newBurger(): Burger {
    return new HamBerger();
  }
}

 

 

위의 코드에서 볼 수 있듯, 다양한 버거들의 레시피에 의존할 수 있는 BurgerChef가 됩니다. 의존 관계를 인터페이스로 추상화하게 되면, 더 다양한 의존 관계를 맺을 수가 있고, 실제 구현 클래스와의 관계가 느슨해지고, 결합도가 낮아집니다.

 

 

 

 

DI를 살펴보자

의존관계가 무엇인지에 대해, 그리고 다양한 의존관계를 위해 인터페이스로 추상화함을 알아봤습니다. 그렇다면, Dependency Injection은 무엇인지 알아보겠습니다.

 

지금까지 구현에서는 BurgerChef 내부적으로 의존 관계인 BurgerRecipe가 어떤 값을 가질지 직접 정하고 있습니다. 만약 어떤 BurgerRecipe를 만들지를 셰프가 아닌 버거 가게 사장님이 정하는 상황을 상상해보겠습니다. 즉, BurgerChef가 의존하고 있는 BurgerRecipe를 외부에서 결정하고 주입해주는 것입니다. 이처럼 의존관계를 외부에서 결정하고 주입하는 것이 DI(의존관계 주입)입니다.

 

 

 

DI 구현 방법

DI는 의존관계를 외부에서 결정하는 것이기 때문에, 클래스 변수를 결정하는 방법들이 곧 DI를 구현하는 방법입니다. 런타임 시점의 의존관계를 외부에서 주입하여 DI 구현이 완성됩니다. Burger 레스토랑 주인이 어떤 레시피를 주입하는지 결정하는 예시로 설명해 보겠습니다.

 

 

class BurgerChef {
  private burgerRecipe: BurgerRecipe;

  public constructor(burgerRecipe: BurgerRecipe) {
    this.burgerRecipe = burgerRecipe;
  }
}

interface BurgerRecipe {
  newBurger(): any;
}

class BurgerRestaurantOwner {
  private burgerChef: BurgerChef = new BurgerChef(new HamburgerRecipe());

  public changeMenu(): void {
    this.burgerChef = new BurgerChef(new CheeseBurgerRecipe());
  }
}

 

 

위의 예시는 생성자를 이용하여 활용하고자 하는 레시피를 주입받고 있습니다. 

 

 

 

DI 장점

만약 DI, 즉 의존 관계를 분리하여 주입을 받는 방법의 코드 구현은 어떤 장점이 있는지 알아보겠습니다. 

 

 

1. 의존성이 줄어든다.

의존한다는 것은 의존 대상의 변화에 취약하다는 것입니다. 만약 DI로 구현하게 됐을 때, 주입받는 대상이 변하더라도 그 구현 자체를 수정할 일이 없거나 줄어들게 됩니다.

 

 

2. 재사용성이 높은 코드가 된다.

기존에 BurgerChef 내부에서만 사용되었던 BurgerRecipe을 별도로 구분하여 구현하면, 다른 클래스에서 재사용할 수가 있습니다.

 

 

3. 테스트하기 좋은 코드가 된다.

BurgerRecipe의 테스트를 BurgerChef 테스트와 분리하여 진행할 수 있습니다.

 

 

4. 가독성이 높아진다.

BurgerRecipe의 기능들을 별도로 분리하게 되어 자연스레 가동성이 높아집니다.

 

 

 

 

 

 

 


 

 

 

 

 

 

 

 

마치며

객체지향 프로그래밍의 필요성을 점점 느끼고 있습니다. 언젠가, 코드에 대한 역할과 책임을 명확하게 보여줄 수 있는 코드를 작성할 수 있다면 동료들이, 일을 더 수월하게 할 수 있지 않을까 생각합니다. 배려가 담긴 코드를 작성하기 위해 꾸준하게 노력하고 싶습니다. 

 

 

 

 

 

 

 


 

 

 

 

 

출처

 

객체 지향과 디자인 패턴 - YES24

객체 지향 안내서. 객체, 책임, 의존, 캡슐화 등 객체 지향의 주요 개념들을 쉬운 예제와 그림을 통해 이해하기 쉽게 설명한다.

www.yes24.com

 

객체 지향 프로그래밍 입문 - 인프런 | 강의

잘 하는 개발자가 되기 위해서는 유연한 코드를 작성할 줄 알아야합니다. 객체 지향을 이용해서 변경하기 좋은 유연한 코드를 만드는 방법을 알아보세요., - 강의 소개 | 인프런...

www.inflearn.com

 

IoC, DI, DIP 개념 잡기 - 기록은 기억을 지배한다

IoC, DIP, DI 는 항상 혼동되는 개념이다. 각 개념을 서로 같다고 표현하는 글들도 제법 있고, 각 개념의 정의를 살펴보니 직관적으로 이해가 되지 않았다. 요즘 특히 DI 에 대한 내용이 많이 언급되

vagabond95.me

 

[객체 지향 프로그래밍 입문] 의존과 DI

기능 구현을 위해 다른 구성 요소를 사용하는 것의존의 예 : 객체 생성, 메서드 호출, 데이터를 사용의존은 변경이 전파될 가능성을 의미의존하는 대상이 바뀌면 바뀔 가능성이 높아짐예 : 호출

velog.io

 

의존관계 주입(Dependency Injection) 쉽게 이해하기

이번 글에서는 DI(의존성 주입, 의존관계 주입)의 개념을 설명한다.

tecoble.techcourse.co.kr