[DI][번역] 안드로이드에서 의존성 주입

sana·2022년 6월 10일
0

Dependency Inejction

목록 보기
2/2

이 글은 아래 기사를 일부 번역한 것입니다.
https://www.techyourchance.com/dependency-injection-android/


안드로이드에서 의존성 주입

의존성 주입은 안드로이드 공식 문서와 가이드라인에서 매우 오랫동안 방치되어 왔지만 최근들어 많은 관심을 받기 시작했습니다.

이는 의심할 여지 없이 환영할 만한 변화이며 플랫폼이 지속적으로 성숙해진다는 징후입니다. 그러나 이러한 때에 좋은 가이드라인이 없으면 의존성 주입 프레임워크를 심각하게 잘못 사용하는 결과를 낳을 수 있습니다. 이 글에서는 안드로이드의 의존성 주입과 관련된 일반적인 실수를 피하는 데 도움이 되는 몇 가지 모범 사례(best practice)를 공유하도록 하겠습니다.

안드로이드에서 의존성 주입을 하기 위해 가장 일반적으로는 Dagger2를 선택하므로 표시되는 코드 스니펫은 해당 구문(syntax)을 사용합니다. 그러나 이러한 모범 사례는 보편적이며 당신이 사용하고자 하는 다른 프레임워크에도 적용된다는 것을 기억하세요.


1. 기본적으로 생성자 주입을 사용하라 (Use constructor injection by default)

클라이언트는 생성자 인수(argument)를 통해 가능할 때는 언제든지 모든 서비스를 요청할 수 있어야 합니다.

생성자 주입의 장점은 다음과 같습니다.

  1. 모든 종속성이 생성자에 분명하게 명시되어 있기 때문에 코드가 더 읽기 쉬워진다.

  2. 컴파일러가 누락된 생성자 인수를 표시해주기 때문에 클라이언트에 서비스를 전달하는 것을 잊을 수 없다.

  3. 생성자 주입된 서비스들은 멀티스레드 환경에서 중요한 것으로 finalized 될 수 있다.

  4. 생성자 인수들은 유닛 테스트에서 쉽게 mock 할 수 있다.

따라서, 첫 번째 규칙은 생성자 주입을 사용하지 않을 특별한 이유가 없는 한 항상 생성자 주입을 사용해야 한다는 것입니다.


2. 안드로이드 최상위 컴포넌트들에서는 필드주입을 사용하라 (Use field injection for Android top-level components)

안드로이드에는 두 가지 "최상위(top-level)" 컴포넌트 그룹이 있습니다.

  • Android 프레임워크가 인스턴스화하는 컴포넌트: Application, Activity, Service
  • Fragmnet

당신이 첫 번째 그룹의 컴포넌트를 직접 인스턴스화하는 것은 아니므로, 생성자 주입을 여기에 사용할 수는 없습니다. 그리고 Fragment는 당신이 인스턴스화할 수 있더라도, "인수가 없는(no-arguments)" 기본 생성자를 사용하여야 하므로, 서비스를 Fragment에 직접 전달 할 수는 없습니다.

생성자 주입을 사용할 수 없으므로, 메서드 주입 또는 필드 주입 중 하나로 돌아가야 합니다. 이러한 경우 필드 주입을 선택하는 것이 좋습니다. 메서드 주입은 어떤 이점도 제공하지 않으면서 코드를 읽고 이해하는 것만 어렵게 만들 수 있기 때문입니다.


3. 커스텀뷰에 주입하기 위해 의존성 주입 프레임워크를 사용하지 마라 (Don’t use dependency injection framework to inject into custom View subclasses)

View의 서브클래스에 삽입할 서비스가 필요한 경우, 이 View를 프로그램적으로(programmatically) 인스턴스화할 수 있다면 생성자 주입을 사용하십시오. 간단합니다.

그러나 View가 XML로 선언된다면 의존성 주입 프레임워크로 해결하지 마십시오. 대신 일반 메서드 주입을 사용하십시오.

예를 들어, 커스텀뷰에 ImageLoader를 삽입해야 하는 경우, 아래 대신

public class SomeClient extends LinearLayout {
    @Inject ImageLoader mImageLoader;
    public SomeClient(Context context) {
        super(context);
        init();
    }
}

이렇게 하십시오.

public class SomeClient extends LinearLayout {
    private ImageLoader mImageLoader;

    public SomeClient(Context context) {
        super(context);
        init();
    }

    public void setImageLoader(ImageLoader imageLoader) {
        mImageLoader = imageLoader;
    }

}

이 경우에서 메소드 주입의 장점은 다음과 같습니다.

  • API 레벨에서 종속성을 볼 수 있다.
  • 메서드 주입은 단일 책임 원칙(Single Responsibility Principle)을 위반하지 않는다.
  • 프레임워크에 대한 종속성이 없다.
  • 성능이 더 좋다.

위의 주장들을 좀 더 분석해 보겠습니다.

첫째로, 메서드 주입을 사용해 주입된 의존성들은 클라이언트의 공개(public) API 중 하나로 나타나고, 소스 코드를 읽는 사람들은 즉시 이를 알 수 있습니다.

둘째로, View의 서브 클래스들이 추가적인 의존성을 필요로 하는 유즈케이스가 많지는 않습니다. 그러나 프레임워크를 사용하여 단 하나의 종속성만 주입하는 것도 기본적으로는 단일 책임 원칙 위반에 대한 문을 열 수 있습니다. 많은 경우, 설계의 품질을 약간 손상시키고 "그 하나의 추가적인 객체(that one addtitional object)"를 커스텀뷰에 주입하는 것은 매우 유혹적일 것입니다.

이러한 작은 타협은 쌓일 것이고, 얼마 후면 커스텀뷰가 UI와 비즈니스 로직의 스파게티로 바뀔 것입니다. 그리고 이런 일이 당신에게 일어나지 않을 거라고 확신한다면, 경험이 부족한 다른 개발자들도 이런 실수를 할 수 있고, 자신들의 실수를 깨닫지 못할 거라는 사실을 잊지 마세요.

세번째 장점은 커스텀뷰를 의존성 주입 프레임워크에 결합하지 않는다는 것입니다. 프레임워크가 교체되거나 완전히 삭제되어야 한다고 가정해 보십시오. 당신이 이미 작업해야 할 수많은 Activity와 Fragment들이 있다는 사실은 이 리팩토링을 매우 큰 프로젝트로 만듭니다. 당신은 분명 이에 더해 수많은 커스텀뷰들까지 처리하고 싶지는 않을 것입니다.

마지막 장점은 성능입니다.

하나의 화면은 하나의 Activity, 여러 개의 Fragment, 수많은 커스텀뷰를 포함하고 있습니다. 이러한 많은 클래스들을 의존성주입 프레임워크를 사용하여 부트스트래핑(Bootstrapping)하면 앱의 성능을 저하시킬 수 있습니다. 리플랙션(reflection) 기반 프레임워크인 경우 특히 그렇고 심지어 Dagger조차 성능비용이 많이 듭니다.


4. 디미터 법칙을 위반하지 마라 (Don’t violate the Law of Demeter)

디미터 법칙은, 의존성 주입의 상황에서 적용될 때 “고객이 필요로 하는 정확한 서비스를 주입해야한다”는 뜻입니다.

안드로이드에서 디미터 법칙은 Context를 클라이언트에 주입하여 다른 객체를 얻을 때 흔하게 위반됩니다.

그러므로, 이렇게 하는 대신에

public class SomeClient {
    private final SharedPreferences mSharedPreferences;

    public SomeClient(Context context) {
        mSharedPreferences =
            context.getSharedPreferences(PREFS_FILE_NAME, Context.MODE_PRIVATE);
    }
}

이렇게 하십시오.

public class SomeClient {
    private final SharedPreferences mSharedPreferences;

    public SomeClient(SharedPreferences sharedPreferences) {
        mSharedPreferences = sharedPreferences;
    }
}

디미터 법칙을 위반하지 않으면 다음과 같은 이점을 줍니다.

  • 클라이언트들의 API는 실제 종속성을 반영한다.
  • 클라이언트들은 "블랙박스(black boxes)"로서 단위 테스트 되어질 수 있다. - 어떤 클래스들이 mock되어야 하는지 알기 위해 그 안의 코드를 읽을 필요가 없다.
  • 일련의 객체들을 mock할 필요가 없으므로 단위 테스트가 쉬워진다.

그것이 정말로 엄격하게 요구되지 않는 한 Context를 전달하지 마세요. 그것이 디미터의 법칙을 따르기 위한 출발점입니다.


5. 객체와 자료구조를 구분하라 (Differentiate between objects and data structures)

Matt Carroll이 여기에서 말했듯이, 자바에서 Object 클래스의 서브클래스들은 두 집단으로 나눠질 수 있습니다: (객체지향적인) 객체(object)와 자료구조(data structure)

객체는 행동을 노출시키고 세부적인 구현은 숨깁니다. 예를 들어, UserManager 클래스는 logIn()이라는 메소드를 노출시킬 수 있습니다.

자료구조는 데이터를 노출시킵니다. 예를 들어 User 클래스는 getFristName(), getLastName() 등과 같은 메소드를 노출시킬 수 있습니다.

의존성 주입은 객체에는 적용할 수 있지만 자료구조에는 적용할 수 없습니다. 나아가 나는 Consturction set이 앱의 자료구조를 알아차려서는 안된다고 말하고 싶습니다. 만약 당신이 Consturction set에서 자료구조를 참조할 수 있는 위치에 있는 경우, 기능적인 로직으로 Consturction set을 망치고 있는 중일 것입니다.


결론

이 글에서 말한 안드로이드에서 의존성 주입에 대한 모범 사례들은 의존성 주입을 더 쉽게 채택하고 일반적인 함정을 피하는 데 도움이 될 것입니다.

이 글의 시작에서 말했듯이 의존성 주입은 객체 지향 설계에서 가장 유익한 아키텍처 패턴 중 하나이며 Android 커뮤니티에서 뜨거운 주제입니다. 제대로 배우면 분리되고 유지 관리 가능한 코드를 작성하는 기술을 향상시킬 수 있습니다. 이것은 직업적으로나 상업적으로나 보람 있는 일입니다.

여기까지입니다. 읽어주셔서 감사합니다.



0개의 댓글