← Назад к вопросам

С какими архитектурами работал

1.2 Junior🔥 221 комментариев
#Архитектура и паттерны#Опыт и софт-скиллы

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Архитектурные подходы в моём опыте

За 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)

Сравнение архитектур

АрхитектураТестируемостьBoilerplateLearningProduction ReadyYear
MVC20%LowEasyNo2014
MVP60%MediumMediumMaybe2016
MVVM80%LowEasyYes2018
MVI100%HighHardYes2019
Clean95%HighHardYes2020
Composable85%LowEasyYes2021

Мой путь и выводы

Эволюция:

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 году.