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
}
}
의존성 주입의 목적
- 의존 대상의 범위 및 라이프사이클 관리: 객체를 공유(예: 상태 공유, 횡단 관심사 분리)하거나 자신보다 라이프사이클이 긴 객체를 사용하기 위해
- 의존성 역전: 모듈의 순환 의존성을 해결하거나 아키텍처에서 정한 의존 방향을 따르기 위해
- 구현 전환: 설정 등에 따른 기능 전환이나 테스트, 디버깅, 검증용 구현으로 교체하기 위해
- 구현 분리: 빌드 속도를 높이거나 독점 라이브러리를 제공하기 위해
불필요한 의존성 주입의 문제점
- 불필요하고 암묵적인 의존성: 생성자 주입 시, 의존 대상의 동작을 추적하려면 생성자의 호출자 확인 필요 → 생성자가 다양한 곳에서 호출되고 있는 경우 모든 호출자 확인에 따른 비용 증가. 다른 방식의 경우도 인터페이스가 분리된 상황에서는 구현을 추적하는 비용 증가
- 호출자의 책임 증가: 생성자나 세터 주입 시, 호출자가 의존성 해결 → 의존성 연쇄적으로 전달 시, 메인 클래스에 모든 의존성이 모임
- 값의 연관성 파괴: ‘여러 객체가 공통 값을 사용하기를 원한다’는 제약 조건이 있더라도 값 주입으로 인해 그 제약 조건 파괴 위험
의존성 주입 시, 목적 명확히 하기
오늘 배운 것
- 관통 프로젝트 발표
- 싸피데이 진행