С какими архитектурами работал
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Архитектурные подходы в моём опыте
За 10+ лет разработки я работал с различными архитектурными паттернами, от старых и устаревших до современных. Расскажу о каждой, их плюсах/минусах и когда применять.
1. MVC (Model-View-Controller) — древняя история
// Activity = View + Controller (проблема!)
class MainActivity : AppCompatActivity() {
private val userList = mutableListOf<User>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Бизнес-логика в Activity
val apiService = Retrofit.create(ApiService::class.java)
apiService.getUsers().enqueue(object : Callback<List<User>> {
override fun onResponse(call: Call<List<User>>, response: Response<List<User>>) {
userList.addAll(response.body()!!)
updateUI() // обновляем UI напрямую
}
override fun onFailure(call: Call<List<User>>, t: Throwable) {}
})
}
}
Проблемы MVC:
- ✗ Activity = God Object с 2000+ строк
- ✗ Нельзя тестировать без Context
- ✗ Утечки памяти из-за Callback
- ✗ Сложно с ориентацией экрана
- ✗ Callback hell
Когда использовал: 2014-2016 годы (когда нечего было лучше)
2. MVP (Model-View-Presenter) — первый шаг
// Presenter отделён от View
interface UserListPresenter {
fun loadUsers()
fun attachView(view: UserListView)
fun detachView()
}
interface UserListView {
fun showUsers(users: List<User>)
fun showError(message: String)
fun showLoading()
}
class UserListPresenterImpl : UserListPresenter {
private var view: UserListView? = null
override fun loadUsers() {
view?.showLoading()
apiService.getUsers(object : Callback<List<User>> {
override fun onResponse(call: Call<List<User>>, response: Response<List<User>>) {
view?.showUsers(response.body() ?: emptyList())
}
override fun onFailure(call: Call<List<User>>, t: Throwable) {
view?.showError(t.message ?: "Error")
}
})
}
}
class UserListActivity : AppCompatActivity(), UserListView {
private val presenter = UserListPresenterImpl()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
presenter.attachView(this)
presenter.loadUsers()
}
override fun showUsers(users: List<User>) {
// Только обновляем UI
}
override fun onDestroy() {
presenter.detachView()
super.onDestroy()
}
}
Улучшения MVP:
- ✓ Presenter отделён и тестируемый
- ✓ View — только UI
- ✓ Меньше logic в Activity
- ✗ Boilerplate код (interface для каждого экрана)
- ✗ Все ещё Callback hell
- ✗ Presenter может остаться в памяти
Когда использовал: 2016-2018 годы
3. MVVM (Model-View-ViewModel) — текущий стандарт
// ViewModel содержит состояние и логику
class UserListViewModel(
private val userRepository: UserRepository
) : ViewModel() {
private val _uiState = MutableStateFlow<UiState>(UiState.Idle)
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
fun loadUsers() {
viewModelScope.launch {
_uiState.value = UiState.Loading
try {
val users = userRepository.getUsers()
_uiState.value = UiState.Success(users)
} catch (e: Exception) {
_uiState.value = UiState.Error(e.message)
}
}
}
}
// Activity/Compose только отображает состояние
class UserListActivity : AppCompatActivity() {
private val viewModel: UserListViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val uiState by viewModel.uiState.collectAsState()
when (uiState) {
is UiState.Loading -> LoadingScreen()
is UiState.Success -> UserListScreen(users = (uiState as UiState.Success).users)
is UiState.Error -> ErrorScreen(message = (uiState as UiState.Error).message)
else -> {}
}
}
viewModel.loadUsers()
}
}
Преимущества MVVM:
- ✓ ViewModel автоматически переживает ротацию
- ✓ Reactive programming (Flow, StateFlow)
- ✓ Полностью тестируемо
- ✓ Отделение логики от UI
- ✓ Встроено в Jetpack
- ✓ Нет memory leaks
- ✓ Асинхрона через Coroutines
Когда использую: 2018-2025 (и дальше)
4. MVI (Model-View-Intent) — Redux для Android
// MVI = Redux pattern (Elm-inspired)
// Intent -> Processor -> Result -> Reducer -> Model -> View
sealed class UserListIntent {
object LoadUsers : UserListIntent()
data class SearchUsers(val query: String) : UserListIntent()
}
sealed class UserListResult {
object Loading : UserListResult()
data class UsersLoaded(val users: List<User>) : UserListResult()
data class Error(val message: String) : UserListResult()
}
data class UserListModel(
val users: List<User> = emptyList(),
val loading: Boolean = false,
val error: String? = null
)
class UserListViewModel : ViewModel() {
val intents = Channel<UserListIntent>()
val models: Flow<UserListModel> = transformIntentsToModels()
private fun transformIntentsToModels(): Flow<UserListModel> {
return intents.consumeAsFlow()
.flatMapLatest { intent ->
when (intent) {
is UserListIntent.LoadUsers -> loadUsersProcessor()
is UserListIntent.SearchUsers -> searchUsersProcessor(intent.query)
}
}
.scan(UserListModel()) { model, result ->
when (result) {
is UserListResult.Loading -> model.copy(loading = true)
is UserListResult.UsersLoaded -> model.copy(users = result.users, loading = false)
is UserListResult.Error -> model.copy(error = result.message, loading = false)
}
}
}
}
Характеристики MVI:
- ✓ Предсказуемые состояния
- ✓ Легко отладить (логируй каждый intent)
- ✓ Deterministic (тестируется как math функция)
- ✗ Много boilerplate
- ✗ Steep learning curve
- ✗ Может быть overengineering для простых экранов
Когда использовал: 2019-2021 (в одном проекте с Redux-like store)
5. Clean Architecture с DDD (Domain-Driven Design)
// Слои с чёткими зависимостями
// Domain Layer (никаких Android зависимостей!)
data class User(val id: String, val name: String, val email: String)
interface UserRepository {
suspend fun getUser(id: String): User
}
interface GetUserUseCase {
suspend operator fun invoke(userId: String): Result<User>
}
// Application Layer
class GetUserUseCaseImpl(
private val repository: UserRepository
) : GetUserUseCase {
override suspend fun invoke(userId: String): Result<User> {
return try {
Result.success(repository.getUser(userId))
} catch (e: Exception) {
Result.failure(e)
}
}
}
// Infrastructure Layer
@Inject
class UserRepositoryImpl(
private val apiService: ApiService,
private val database: UserDatabase
) : UserRepository {
override suspend fun getUser(id: String): User {
return withContext(Dispatchers.IO) {
val cached = database.userDao().getUser(id)
if (cached != null) return@withContext cached
val remote = apiService.getUser(id)
database.userDao().insert(remote)
remote
}
}
}
// Presentation Layer
@HiltViewModel
class UserViewModel @Inject constructor(
private val getUserUseCase: GetUserUseCase
) : ViewModel() {
private val _uiState = MutableStateFlow<UiState>(UiState.Idle)
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
fun loadUser(userId: String) {
viewModelScope.launch {
val result = getUserUseCase(userId)
_uiState.value = when {
result.isSuccess -> UiState.Success(result.getOrNull()!!)
result.isFailure -> UiState.Error(result.exceptionOrNull()?.message)
else -> UiState.Idle
}
}
}
}
Преимущества Clean Architecture:
- ✓ Максимальная тестируемость
- ✓ Independence от фреймворков
- ✓ Domain logic вообще не знает про UI
- ✓ Легко переиспользовать Use Cases
- ✓ Масштабируемость на долгосрок
- ✗ Может быть overengineering для MVP
- ✗ Много interfaces и abstraction
Когда использую: 2020-2025 (в production проектах)
6. Composable Architecture (современный Compose подход)
// Вместо иерархии Activity/Fragment используем Composable
@Composable
fun UserListScreen(
viewModel: UserListViewModel = hiltViewModel()
) {
val uiState by viewModel.uiState.collectAsState()
LaunchedEffect(Unit) {
viewModel.loadUsers()
}
when (uiState) {
is UiState.Loading -> LoadingScreen()
is UiState.Success -> {
val users = (uiState as UiState.Success).users
LazyColumn {
items(users, key = { it.id }) { user ->
UserCard(
user = user,
onClick = { /* navigate */ }
)
}
}
}
is UiState.Error -> ErrorScreen(message = (uiState as UiState.Error).message)
}
}
@Composable
fun UserCard(user: User, onClick: () -> Unit) {
Card(modifier = Modifier.clickable(onClick = onClick)) {
Column(modifier = Modifier.padding(16.dp)) {
Text(user.name, fontSize = 18.sp, fontWeight = FontWeight.Bold)
Text(user.email, color = Color.Gray)
}
}
}
Характеристики Composable:
- ✓ Declarative (как React)
- ✓ Реактивность встроена
- ✓ Меньше boilerplate
- ✓ Preview для отладки
- ✓ Compose навигация
- ✓ Логичное разделение компонентов
Когда использую: 2021-2025 (для новых feature)
Сравнение архитектур
| Архитектура | Тестируемость | Boilerplate | Learning | Production Ready | Year |
|---|---|---|---|---|---|
| MVC | 20% | Low | Easy | No | 2014 |
| MVP | 60% | Medium | Medium | Maybe | 2016 |
| MVVM | 80% | Low | Easy | Yes | 2018 |
| MVI | 100% | High | Hard | Yes | 2019 |
| Clean | 95% | High | Hard | Yes | 2020 |
| Composable | 85% | Low | Easy | Yes | 2021 |
Мой путь и выводы
Эволюция:
MVC (2014-2016) →
MVP (2016-2018) →
MVVM (2018-2020) →
MVVM + Clean Architecture (2020-2023) →
MVVM + Clean + Compose (2023-2025)
Что выбираю для нового проекта в 2025:
- ✓ Архитектура: MVVM + Clean Architecture
- ✓ UI: Jetpack Compose
- ✓ Асинхрона: Kotlin Coroutines
- ✓ DI: Hilt
- ✓ Navigation: Compose Navigation
- ✓ Database: Room
- ✓ Testing: JUnit 5 + Mockk
Ключевой вывод: Не существует идеальной архитектуры. Выбирай в зависимости от:
- Размера команды
- Сложности проекта
- Временных ограничений
- Опыта разработчиков
Для большинства Android проектов MVVM + Clean Architecture + Compose — оптимальный выбор в 2025 году.