Android에서 SOLID 원칙을 적용하는 15가지 실전 사례
Android 개발자로서 SOLID 원칙에 대해 자주 듣지만, 실제 프로젝트에 어떻게 적용해야 하는지는 명확하지 않을 때가 많습니다. SOLID는 소프트웨어를 더 유지보수 가능하고, 유연하며, 확장 가능하게 만드는 데 도움이 되는 다섯 가지 설계 원칙의 집합입니다. 이 원칙들은 특히 대규모 코드베이스에서 작업하거나 더 나은 테스트 가능성과 관심사의 분리를 원할 때 Android에서 강력한 도구가 됩니다.
이 글에서는 각 SOLID 원칙에 대해 Android에서의 세 가지 실전 사례를 살펴보겠습니다. Kotlin 코드 예제와 함께 자세히 설명하므로, Jetpack Compose UI를 구축하거나 API를 통합하거나 ViewModel 및 UseCase를 설정할 때 바로 적용할 수 있는 실용적인 지식을 얻을 수 있습니다.
---
1. 단일 책임 원칙 (SRP)
클래스는 변경할 이유가 하나만 있어야 합니다.
Android 개발에서는 Activity, Fragment, ViewModel 등이 UI, 네비게이션, API 호출, 오류 처리 등을 모두 처리하면서 클래스가 쉽게 비대해집니다. SRP는 각 클래스가 하나의 책임만을 가지도록 분리할 것을 권장합니다.
✅ 사례 1: ViewModel의 UI와 비즈니스 로직 분리
class LoginUseCase(private val repository: LoginRepository) {
suspend fun login(username: String, password: String): Result<User> {
if (username.isEmpty()) return Result.failure(Exception("Username empty"))
return repository.login(username, password)
}
}
class LoginViewModel(private val useCase: LoginUseCase) : ViewModel() {
val uiState = MutableStateFlow<UiState>(UiState.Idle)
fun login(username: String, password: String) {
viewModelScope.launch {
uiState.value = UiState.Loading
val result = useCase.login(username, password)
uiState.value = if (result.isSuccess) UiState.Success else UiState.Error
}
}
}
이유: 비즈니스 로직을 UseCase로 이동시켜 ViewModel은 UI 상태 관리에만 집중하게 합니다. 이렇게 하면 테스트 가능성과 재사용성이 향상됩니다.
✅ 사례 2: API 호출을 Repository에서 분리
interface RemoteDataSource {
suspend fun login(username: String, password: String): Result<User>
}
class LoginRepository(private val remote: RemoteDataSource) {
suspend fun login(username: String, password: String) = remote.login(username, password)
}
이유: API 호출 책임을 RemoteDataSource에 위임하여 Repository는 데이터 조정에만 집중하게 합니다. 이렇게 하면 테스트 시 원격 로직을 쉽게 대체하거나 모킹할 수 있습니다.
✅ 사례 3: Fragment에서 책임 분리
class ProfileFragment : Fragment() {
private val viewModel: ProfileViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
setupUi()
observeViewModel()
}
private fun setupUi() { /* 뷰 설정 및 리스너 */ }
private fun observeViewModel() { /* 플로우 수집 */ }
}
이유: 모든 로직을 onViewCreated에 몰아넣는 대신, 각 메서드에 특정 작업을 할당하여 가독성을 높이고 Fragment가 SRP를 따르도록 합니다.
---
2. 개방-폐쇄 원칙 (OCP)
소프트웨어 엔티티는 확장에는 열려 있어야 하고, 수정에는 닫혀 있어야 합니다.
기존 코드를 수정하지 않고 새로운 기능을 추가할 수 있어야 합니다. 이렇게 하면 버그 발생 위험이 줄고 코드베이스가 미래에도 유연하게 유지됩니다.
✅ 사례 1: UI 상태를 위한 Sealed 클래스 확장
sealed class UiState {
object Loading : UiState()
data class Success(val data: String) : UiState()
data class Error(val message: String) : UiState()
}
data class Empty(val reason: String) : UiState()
이유: Empty와 같은 새로운 화면 상태를 기존 코드를 수정하지 않고 추가할 수 있습니다. Sealed 클래스를 사용하면 새로운 상태를 쉽게 확장할 수 있습니다.
---
3. 리스코프 치환 원칙 (LSP)
하위 클래스는 상위 클래스의 기능을 대체할 수 있어야 합니다.
하위 클래스가 상위 클래스의 기능을 대체할 수 있어야 하며, 이는 코드의 유연성과 재사용성을 높입니다.
✅ 사례 1: 사용자 정의 뷰에서 상위 클래스 대체
open class BaseButton(context: Context) : Button(context) {
open fun customize() { /* 기본 커스터마이징 */ }
}
class PrimaryButton(context: Context) : BaseButton(context) {
override fun customize() { /* 기본 커스터마이징 + 추가 스타일 */ }
}
이유: PrimaryButton이 BaseButton을 완전히 대체할 수 있으므로, LSP를 준수합니다. 이는 사용자 정의 뷰를 만들 때 유용합니다.
---
4. 인터페이스 분리 원칙 (ISP)
클라이언트는 사용하지 않는 인터페이스에 의존하지 않아야 합니다.
인터페이스를 세분화하여 클라이언트가 자신이 사용하는 메서드에만 의존하도록 합니다.
✅ 사례 1: 인터페이스를 기능별로 분리
interface Clickable {
fun click()
}
interface LongClickable {
fun longClick()
}
class Button : Clickable {
override fun click() { /* 클릭 동작 */ }
}
이유: Button은 Clickable만 구현하므로, 사용하지 않는 LongClickable에 의존하지 않습니다. 이는 ISP를 준수합니다.
---
5. 의존성 역전 원칙 (DIP)
고수준 모듈은 저수준 모듈에 의존해서는 안 되며, 둘 다 추상화에 의존해야 합니다.
구현 세부 사항이 아닌 추상화에 의존하여 모듈 간 결합도를 낮춥니다.
✅ 사례 1: 인터페이스를 통한 의존성 주입
interface AuthService {
fun authenticate(user: String, password: String): Boolean
}
class FirebaseAuthService : AuthService {
override fun authenticate(user: String, password: String): Boolean {
// Firebase 인증 로직
return true
}
}
class LoginManager(private val authService: AuthService) {
fun login(user: String, password: String): Boolean {
return authService.authenticate(user, password)
}
}
이유: LoginManager는 AuthService 인터페이스에 의존하므로, 구현 세부 사항에 의존하지 않습니다. 이는 DIP를 준수합니다.
---
태그: Android, SOLID, Kotlin, Clean Architecture, MVVM, Jetpack Compose, Software Design
출처 및 원문 링크: Top 3 Android Use Cases for Every SOLID Principle (with Code)