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

Lee Han Sol·2021년 9월 19일
0
post-thumbnail
post-custom-banner

객체 지향 프로그래밍 입문

이 글은 최범균님의 Inflearn 강의를 학습한 내용을 정리하였습니다.

의존

의존이란

의존이란 기능 구현을 위해 다른 구성 요소를 사용하는 것을 말한다.

의존의 예는 객체 생성, 메소드 호출, 데이터 사용 등이 있다.

의존한다는 것변경이 전파될 가능성이 있음을 의미한다.
예를들어 아래와 같은 경우

  • 호출하는 메소드의 파라미터가 변경
  • 호출하느 메소드에서 발생하는 예외가 추가

의존하는 객체의 코드도 변경이 필요하다.

주의 사항

순환 의존

일단 의존하게되면 변경이 전파될 가능성이 있기 때문에 의존이 순환되지 않도록 해야된다.

순환 의존이란
아래 그림과 같이 의존관계가 순환하는 것을 말한다.

A가 B에 의존하고 B는 C 그리고 C는 다시 A에 의존하고 있다.

만약 A에 변경이 일어났다면, B에 영향을 주고 이어서 C에 영향을 주고 다시 A에 영향을 줄 수 있다.
위와 같은 현상을 방지하기 위해 순환 의존은 클래스, 패키지, 모듈 등 모둔 수준에서 발생하지 않도록 설계해야 한다.

많은 의존

순환 의존뿐만 아니라 의존하는 곳이 많아도 변경의 포인트가 굉장히 많아진다.
아래 그림을 통해 알아보자.

X는 A, B, C, D, E, F를 의존하고 있다.
만약 A ~ F 중에서 A, B, E에서 변경이 일어난다면 X는 의존 대상의 변경에 영향을 받을 수 있다.
그래서 의존 대상최소한이 되도록 설계해야 한다.

의존 최소화 방법

한 클래스에서 많은 기능을 제공하는 경우에 의존 대상이 많아질 수 있다.
이 경우에 의존 대상을 줄일 수 있는 방법을 알아보자.

기능이 많은 경우

예제 코드는 아래와 같다.

발생 문제

이런 경우 각 기능마다 의존하는 대상이 다를 수 있다.
위 코드에서는 regist()와 changePw()가 의존하는 대상이 각각 RegReq, ChangeReq로 다르다.

이런 구조에서는 한 기능을 변경하는 과정에서 다른 기능에 영향을 줄 가능성이 있다.
예를들어, changePw()의 로직을 변경할 때 blockUser()의 코드를 변경해야되는 경우가 생길 수 있다.
이렇게 다른 기능에 의존하게 되는 경우까지 겹치면 독립된 기능 테스트 작성도 힘들어진다.

해결 방법

기능이 많은 경우 해결 방법은 기능 별로 분리하는 것이다.

위와 같이 기능 별로 분리하면 아래와 같은 장점이 있다.

  1. 각 클래스마다 필요한 의존이 줄어든다. (단, 클래스는 증가한다.)
  2. 개별 기능을 테스트하는 것이 쉬워진다.

의존 대상이 많은 경우

예제 코드는 아래와 같다.

해결 방법

몇 가지 의존 대상을 단일 기능으로 묶어보자.

왼쪽 그림에서 아래 2개의 의존 대상은 민원과 관련되어있다.
이 2개의 항목을 오른쪽 그림처럼 하나의 민원으로 기능을 추상화하면 의존 대상이 줄어든다.


여기까지는 의존 대상이 많을 경우 생기는 문제와 해결방법에 대하여 알아보았다.
다음은 의존 대상을 주입하는 방법에 대해 알아보자.

DI (Dependency Injection)

DI의 필요성
의존 대상을 직접 생성할 경우 생성 클래스가 바뀌면 의존하는 코드도 바뀌게 된다.

따라서, 코드를 변경하지 않기 위해서 의존 대상을 직접 생성하지 않는 방법이 필요하다.
방법은 아래와 같다.

  • 팩토리, 빌더
  • 의존 주입 (Dependency Injection)
  • 서비스 로케이터 (Service Locator)

이 글에서는 의존 주입 방식을 알아본다.

의존 주입 방법

의존하는 대상을 직접 생성하지 않고, 생성자 또는 메소드를 이용해서 의존성을 주입받는다.
아래의 DI 예제 코드를 보자.

왼쪽의 ScheduleService는 2개의 의존 대상을 갖는다.
그리고 의존성 주입 방식은 생성자 방식, Setter 방식을 사용한다.

  • 생성자 방식으로 UserRepository
  • Setter 방식으로 Calculator

를 주입받고 있다.

이렇게 의존 주입 방식을 사용하면 얻는 장점UserRepository의 구현체가 변경되더라도 ScheduleService의 코드는 변경하지 않아도 된다.

DI와 조립기(Assembler)

조립기
의존성 주입을 위해 객체를 생성하고 의존성 주입을 담당하는 객체이다.

조립기 예: 스프링 프레임워크

아래의 예는 스프링 프레임워크에서 조립기를 이용해 DI를 수행하는 코드이다.

왼쪽의 코드는 스프링에서 객체를 생성하고 의존 대상을 주입하는 설정 클래스이다.
설정 코드를 이용한 ApplicationContext 조립기를 생성한다.

의존성 주입은 설정 클래스를 이용해서 조립기를 초기화하는 시점에 해준다.
(초기화 이후에는 조립기에서 필요한 객체를 구하고 사용하면 된다.)

DI 장점

DI의 장점을 알아보자.

OCP가 지켜진다.

의존 대상이 바뀌면 조립기(설정)만 변경하면 된다.

위 코드는 OrderService의 Notifier 구현체를 Email에서 Email + Kakao 변경하는 예이다.

왼쪽 하단의 Config를 보면 EmailNotifier를 주입했다.
OrderService에서 Notifier를 Email + Kakao을 사용하도록 변경하려면 Config에서 Notifier를 CompositeNotifier로 바꿔주면 된다.

테스트가 쉽다.

DI 방식을 이용하면 의존 객체를 주입할 수 있기 때문에 대역 객체를 사용해서 테스트가 가능하다.

아래의 예는 ScheduleService 테스트를 위해 UserRepository를 주입해주는 테스트 코드이다.

만약 회원 관리를 위한 Repository가 DI 방식이 아니었더라면 테스트를 위해 회원 DB까지 연동해야 될 것이다.

대역 객체를 사용했을 때 추가적인 장점은 원하는 상태로 초기화한 객체를 주입해 다양한 케이스를 테스트할 수 있다.

정리

의존과 DI에 대해서 알아보았다.

의존 관리에 주의사항 2가지 경우를 지키지 않을 때 발생하는 문제에 대해 정리하였다.

  1. 순환 의존
  2. 많은 의존

그리고 의존이 많은 경우 최소화하는 2가지 방법에 대해서 알아보고 DI가 필요한 이유에 대해서 정리하였다.

의존 객체를 최소화하도록 설계하고 의존 객체는 주입받도록 코드를 작성하자.

profile
주 7일, 배움엔 끝이 없다
post-custom-banner

0개의 댓글