의존성 주입(DI)

YounDitt·2020년 9월 20일
0

DI(dependency Injection)를 이용한 빈 의존성 관리

1. Dependency (의존, 의존성)

모듈간의 연결로, A클래스가 객체를 만들기 위해 B를 필요로 할 때, B는 A의 의존(Dependency)의 대상이 된다.

2. Injection (주입)

class Car {
	Tire mTire;
    Car(int tireSize){
    	mTire = new Tire(tireSize);
    }
    void drive(){
    }
}

예) 현대자동차에서 타이어를 만들지 않는다. 금호타이어같은 회사에 주문해서 받아와 조립할 뿐

즉, Car 내부에서 Tire객체를 만드는 것이 아니라, 외부에서 Tire객체를 만들어 받아와서 Car 클래스를 생성하도록 한다.
이렇게 외부에서 의존관계가 있는 대상을 가져오는 것을 Dependency를 외부에서 Inject해 준다고 한다.
(내부에 변수로 다른 Class를 사용)

3. 단점

  • 타이어를 만드는데 tireRubber도 필요하게 되면, Tire클래스 변경때문에 Car클래스 변경이 필요하다.
  • 자동차를 만드는데 타이어, 엔진, 모터가 필요하다고 할때 의존성 때문에 만들어진 자동차에 문제가 생겼을 때 타이어의 문제인지 엔진의 문제인지 파악이 어렵다.

4. Dependency Injection의 구현

class Main{
	public static void main(String[] args){
    	Tire mTire = new Tire(88); //생성자 주입
        Car mCar = new Car(mTire); 
        mCar.drive();
        
        mCar.mEngine = new Engine(); // 필드주입
        mCar.start();
    }
    
    class Car {
        Tire mTire;
        Engine mEngine;
      
        Car(Tire tire){
            mTire = tire;
        }
        void drive(){
        }
        
        void start(){
        mEngine.start();
    }
}

라이브러리를 사용하는 이유

자동차에는 엄청나게 많은 부품이 들어간다. 이를 클래스로 만들면 Car class에 많은 부품을 객체로 받아들일 수 있는 생성자나 setter메소드가 필요하다.
그럼 그에 따라 생성자나 setter메소드를 모두 관리해 주어야 한다.

이러한 부분에서 효율성을 높이기 위해, 올바른 순서로 생성시키고 객체를 받아들일 수 있도록 관리하는 라이브러리를 사용한다.

5. DI의 장점

  • Car클래스에서는 특별히 다른 타이어에 따라 코드를 수정할 필요 없이, Inject(주입)받아서 사용만 하면 된다.
  • 코드 재사용 가능(클래스 재사용 및 종속 항목 분리)
    : 종속항목구현을 쉽게 교체할 수 있다.
    : 컨트롤 반전으로 인해 코드 재사용성이 개선되었다.
    : 클래스가 더이상 종속항목생성방법을 제어하지 않지만 대신 모든 구성에서 작동한다.
  • 리팩터링 편의성
    : 종속항목은 API 노출영역의 검증가능한 요소가 되므로 구현 세부정보로 숨겨지지 않고 객체 생성 타임 또는 컴파일 타임에 확인할 수 있다.
  • 테스트 편의성
    : 클래스는 종속항목을 관리하지 않으므로 테스트 시 다양한 구현을 전달하여 다양한 모든 사례를 테스트 할 수 있다.

6. 주입방법

  1. 생성자 삽입 : 클래스의 종속항목을 생성자에 전달한다.
  2. 필드삽입(또는 setter 삽입) : 활동 및 프래그먼트 등 특정 Android 프레임워크 클래스는 시스템에서 인스턴스화하므로 생성자삽입이 불가능하다. 필드삽입을 사용하면 종속항목은 클래스가 생성된 후 인스턴스화된다.

🙋‍♀️ DI는 어떤 디자인 패턴을 사용할까?

전략패턴 참고
장점 : 의존하고 있는 클래스가 변경되더라도 인터페이스를 통해서 의존하고 있기 대문에, B클래스를 바꾸고 싶다면 의존설정 시 에 다른 클래스만 명시하면 된다.
이렇게 설정을 하면 OCP와 DIP를 자연스럽게 따르게 된다.

🙋‍♀️ DI의 개념에서의 DIP(Dependency Inversion Principle)

  • DI + 인터페이스(추상화)
  • 인터페이스(추상화)에서는 의존성 정보를 갖고 있으면 안된다.
  • 구체적인 의존관계가 추상화에 의해 런타임에 결정되기 대문에 다형성을 적극적으로 활용할 수 있으며 모듈의 재사용성, 테스트용이성이 높아진다.

🙋‍♀️ IoC를 사용했을때 vs 안했을때(아직 미흡)

= IoC(제어역전)를 구현하는 기술들
1. 팩토리 패턴 사용
2. 서비스 로케이터 패턴 사용 : 의존성이 필요한 시점에 service locator에 요청
3. DI사용 : 생성자주입, 변수주입, 인터페이스주입

Koin

  • Annotaion Processing을 통한 코드생성

  • Reflection을 사용하지 않기에 Dagger보다 가볍다.

  • Activity에서만 Inject할 수 있다.

  • 다른 class type에서 inject하려면 관련된 constructor를 반드시 제공해야 한다.
    (->Koincomponent로 해결가능)

1. Single & Factory

  • 객체를 생성해 주입시켜줄 Module에 사용되는 키워드
  1. Single
    • Dagger의 Singleton 어노테이션과 비슷
    • Single Instance를 제공하며 전역적으로 사용
  2. Factory
    • Dagger의 Provides와 비슷
    • Inject시 매번 instance를 생성해서 사용

🙋‍♀️ Single로는 동일 타입의 Instance는 한번 만 만들 수 있을까?

동일타입의 Instance가 여러개 필요하다면 qualifier를 지정하여 만들 수 있다.

single(named("first")){MyClass()}
single(named("second")){MyClass()}

val first : MyClass = get(named("first"))
val second : MyClass = get(named("second"))

2. Module 구현

  • 객체를 만들어서 주입해 줄 대상들에 대한 목록
  • 객체생성은 요청시 lazy하게 생성된다.
    (craetedAtStart 프로퍼티로 정의하는 동시에 생성할 수 있다.)

3. 그 외 DSL 키워드

  • scoped : scope 내에서 단일 instance를 반환
  • bind : 생성할 instance를 다른 타입으로 binding 할 때 사용 (상속관계가 있어야 한다?)
  • get : 주입할 각 컴포넌트의 의존성을 해결하기 위해 사용

3. 객체 주입

  1. 변수 선언 시 by inject()태그를 붙여서 lazy하게 호출하며 생성한다.
  2. get()태그를 붙여서 바로 주입받는다.

4. Viewmodel과 Koin

Dagger2

  • 컴파일 타임에 종송학목을 연결하는 코드를 생성하는 정적 솔루션
  • annotaion processing을 사용해서 컴파일타임에 수동의존성주입과 비슷한 코드를 생성해준다.
  • 런타임 오류가 발생하지 않는다.
  • 의존성 사이클이 발생하는것을 방지한다.

간략하게만..

  • HiltAndroidApp,AndroidEntryPoint : 진입지점 생성
  • Module : 의존성 생성 class
  • Inject, Binds, Provides : 의존성 생성 method(생성자 호출)
  • Component : Module 실행, 관리, 연결
    - Custom Scope Animation은 생명주기에 맞는 이름으로 만들면 재사용하기 좋다?

👀 Koin vs Dagger2

1. Koin

  • 모듈에서 선언한 DI를 Cache에 저장하고 있다.
  • 처음 앱을 구동할 때 한번 캐싱한다.
  • Injection대상필드가 주입하기 위해 객체를 가져오는 Pull방식(private 접근)

2. Dagger

  • Koin과는 반대로 DI가 어디에 injection되어야 하는지 Component Interface를 통해 지정한다.
  • Code Generation방식으로 인터페이스와 어노테이션을 통해 코드를 만들기 때문에 Cache가 필요없으며 속도가 빠르다.
  • Inject를 바깥에서 주입하는 push 방식(public 접근)
  • Injection 대상 필드는 반드시 public 으로 접근제한을 풀어줘야 한다.

Hilt

  • Jetpack 라이브러리로 Android 클래스에 컨테이너를 제공하고 수명주기를 자동으로 관리함.
  • annotaion processing을 사용해서 컴파일타임에 수동의존성주입과 비슷한 코드를 생성해준다.
  • Dagger에 비해 불필요한 annotation을 제거하여 러닝커브를 낮췄다.
    참고

간략하게만..

  1. 의존성을 주입할 시작점을 정한다.(@HiltAndroidApp for application, @AndroidEntryPoint for activity)
  2. constrouctor로 의존성을 주입받는 포인트를 선언한다.(@Inject)
  3. 의존성을 생성할 Module을 정의한다.(@Module, @Provides, @Binds)

참고 1
참고 2
참고 3
참고 4
koin to hilt
참고 5
참고 6

profile
Hello, Android

0개의 댓글