2025-12-26

1일 1아티클

LY

의존성 주입의 목적

문제 상황


  class LatestNewsSnippetUseCase(
      private val locale: Locale,
      private val articleRepository: NewsArticleRepository,
      private val sourceRepository: NewsSourceRepository,
      private val stringTruncator: StringTruncator,
      private val timeFormatter: TimeTextFormatter = TimeTextFormatterImpl(locale),
      private val modelFactory: (title: String, content: String, dateText: String, source: String) -> NewsSnippet =
          ::NewsSnippet
  ) {
      fun getLatestSnippet(): NewsSnippet {
          val article = articleRepository.getLatestArticle()
          val articleText = stringTruncator.truncate(article.contextText, ARTICLE_TEXT_LENGTH, locale)
          val dateText = timeFormatter.toShortMonthDayText(article.timestampInMillis)
          val sourceName = sourceRepository.getSource(article.sourceId).shortName
          return modelFactory(article.title, articleText, dateText, sourceName)
      }

      companion object {
          private const val ARTICLE_TEXT_LENGTH = 280
      }
  }

  interface StringTruncator {
      fun truncate(string: String, length: Int, locale: Locale, suffix: String = "…" /* U+2026 */): String
  }

  interface TimeTextFormatter {
      fun toShortMonthDayText(millis: Long): String
  }

  class NewsSnippet(val title: String, val content: String, val dateText: String, val source: String)

개선 방향

  • 위 코드에서, 외부에서 전달할 수 있도록 만들어야 하는 것은 환경에 따라 달라지는 값복잡한 클래스
  • modelFactory: NewsSnippet의 생성자를 직접 호출한다.
  • stringTruncator/timeFormatter: 인스턴스를 일반적인 private 속성으로 유지한다.
    • 인터페이스와 구현을 통합하는 것도 고려한다.
    • StringTruncator의 경우 상태를 갖지 않는다면 object로 변경해도 괜찮다.

개선 결과


  class LatestNewsSnippetUseCase(
      private val locale: Locale,
      private val articleRepository: NewsArticleRepository,
      private val sourceRepository: NewsSourceRepository
  ) {
      private val stringTruncator: StringTruncator = StringTruncatorImpl()
      private val timeFormatter: TimeTextFormatter = TimeTextFormatterImpl(locale)
      
      suspend fun getLatestSnippet(): NewsSnippet {
          val article = articleRepository.getLatestArticle()
          val articleText = stringTruncator.truncate(article.contextText, ARTICLE_TEXT_LENGTH, locale)
          val dateText = timeFormatter.toShortMonthDayText(article.timestampInMillis)
          val sourceName = sourceRepository.getSource(article.sourceId).shortName
          return NewsSnippet(article.title, articleText, dateText, sourceName)
      }

      companion object {
          private const val ARTICLE_TEXT_LENGTH = 280
      }
  }

의존성 주입의 목적

  • 의존 대상의 범위 및 라이프사이클 관리: 객체를 공유(예: 상태 공유, 횡단 관심사 분리)하거나 자신보다 라이프사이클이 긴 객체를 사용하기 위해
  • 의존성 역전: 모듈의 순환 의존성을 해결하거나 아키텍처에서 정한 의존 방향을 따르기 위해
  • 구현 전환: 설정 등에 따른 기능 전환이나 테스트, 디버깅, 검증용 구현으로 교체하기 위해
  • 구현 분리: 빌드 속도를 높이거나 독점 라이브러리를 제공하기 위해

불필요한 의존성 주입의 문제점

  • 불필요하고 암묵적인 의존성: 생성자 주입 시, 의존 대상의 동작을 추적하려면 생성자의 호출자 확인 필요 → 생성자가 다양한 곳에서 호출되고 있는 경우 모든 호출자 확인에 따른 비용 증가. 다른 방식의 경우도 인터페이스가 분리된 상황에서는 구현을 추적하는 비용 증가
  • 호출자의 책임 증가: 생성자나 세터 주입 시, 호출자가 의존성 해결 → 의존성 연쇄적으로 전달 시, 메인 클래스에 모든 의존성이 모임
  • 값의 연관성 파괴: ‘여러 객체가 공통 값을 사용하기를 원한다’는 제약 조건이 있더라도 값 주입으로 인해 그 제약 조건 파괴 위험

의존성 주입 시, 목적 명확히 하기

오늘 배운 것

  1. 관통 프로젝트 발표
  2. 싸피데이 진행

내일 할 일

참고자료

results matching ""

    No results matching ""