개발/소프트웨어 디자인 패턴

설계 패턴에서 Interface와 implementation을 쓰는 이유

뿌꾸 빵 2025. 2. 18. 17:16
728x90
반응형

아키텍처 설계를 하는데 굳이 Interface와 구현 클래스를 나누는 이유는 무엇일까 궁금해졌다.

Kotlin에서는 이것이 일반적인 방식의 설계 패턴인데, 유연한 구조를 만들고 유지보수가 편해진다.

1️⃣ 유지보수 & 확장성

예제 1: 인터페이스 없이 직접 구현한 경우

class LoginRepository @Inject constructor(
    private val api: LoginNetworkApi
) {
    suspend fun getLoginData(): LoginClientModel {
        return api.getLoginData()
    }
}

이렇게 하면 LoginRepository 안에서 직접 LoginNetworkApi를 사용하니까 나중에 구현을 바꾸기 어려움

예제 2: interface를 사용한 경우 (좋은 예!)

interface LoginRepository {
    suspend fun getLoginData(): LoginClientModel
}

class LoginRepositoryImpl @Inject constructor(
    private val api: LoginNetworkApi
) : LoginRepository {
    override suspend fun getLoginData(): LoginClientModel {
        return api.getLoginData()
    }
}

이렇게 interface를 두면:

  • API 방식이 바뀌더라도 LoginRepositoryImpl만 수정하면 됨
  • 테스트할 때도 FakeLoginRepository를 쉽게 만들어서 대체 가능
  • DB에서 가져오는 LocalLoginRepositoryImpl을 만들어도 구조 변경 없이 추가 가능

2️⃣ 테스트하기 쉬워짐 (Mocking 가능)

인터페이스 없이 테스트하면?

val repository = LoginRepository(LoginNetworkApi()) // 의존성이 직접 결합됨
  • 여기서 LoginRepository는 LoginNetworkApi를 직접 호출하니까,
  • 테스트할 때 진짜 네트워크 요청이 나감! 😱

👉 네트워크가 없으면 테스트가 실패함. 속도도 느림.

인터페이스를 활용하면?

class FakeLoginRepository : LoginRepository {
    override suspend fun getLoginData(): LoginClientModel {
        return LoginClientModel(name = "Test User", token = "fake_token")
    }
}
val repository: LoginRepository = FakeLoginRepository() // 네트워크 없이 테스트 가능!
  • FakeLoginRepository를 만들어서 네트워크 없이 테스트 가능
  • 테스트 속도가 빨라짐 & 안정적

3️⃣ 의존성 주입 (DI, Dependency Injection) 활용

Hilt 같은 DI를 사용할 때 인터페이스를 쓰면 쉽게 구현체를 교체할 수 있다.

Hilt에서 인터페이스를 주입하는 방식

@Module
@InstallIn(SingletonComponent::class)
object RepositoryModule {

    @Provides
    fun provideLoginRepository(api: LoginNetworkApi): LoginRepository {
        return LoginRepositoryImpl(api)
    }
}

이렇게 하면

  • LoginRepositoryImpl을 다른 구현체로 쉽게 교체 가능
  • Mock Repository를 주입해서 테스트 가능
728x90
반응형