SoC (Separation of Concerns) — это аббревиатура принципа, при котором программная система делится на изолированные области ответственности. По данным Martin Fowler, разделение ответственности — ключевой элемент ремонтопригодного кода. Принцип SoC позволяет разработчикам менять один слой приложения, не затрагивая остальные, что особенно важно в командной мобильной разработке.
Главное
SoC расшифровывается как Separation of Concerns — «разделение ответственности» или «разделение областей интереса». В контексте разработки термин concern обозначает любую отделимую функциональность: отображение пользовательского интерфейса, обработка нажатий, валидация данных, сетевое взаимодействие или работа с базой. Принцип SoC предписывает группировать код вокруг этих областей так, чтобы изменения в одной не затрагивали другие.
Аббревиатура SoC широко используется в технической литературе, архитектурных обсуждениях и документации фреймворков. Например, в документации Android Architecture Components многократно упоминается SoC как мотивация для разделения ViewModel и View. В iOS-сообществе термин используется при обсуждении проблем Massive View Controller — прямого следствия отсутствия SoC.
Важно понимать, что SoC — это не единоразовое действие, а непрерывный процесс. По мере роста приложения появляются новые области ответственности, и архитектуру приходится пересматривать. Хорошая кодовая база проходит несколько итераций разделения, прежде чем достигает стабильного состояния, где каждый concern изолирован и управляем.
Separation of Concerns и его аббревиатура SoC обозначают один и тот же принцип. Разница только в контексте использования: полное название применяется в формальных документах, обучающих материалах и впервые при объяснении концепции новым разработчикам. SoC удобен в технических дискуссиях, код-ревью и документации, где краткость важна.
В профессиональной среде оба термина взаимозаменяемы. Разработчик может сказать «здесь нарушен SoC» или «это нарушает Separation of Concerns» — смысл не меняется. Однако в вакансиях и требованиях к архитектуре чаще встречается полное название, тогда как в чатах и код-ревью — аббревиатура. Знание обоих вариантов необходимо для комфортного вхождения в индустрию.
Существует терминологическая путаница: аббревиатура SoC также используется в аппаратном контексте для System-on-a-Chip (система на кристалле). В мобильной разработке контекст всегда понятен из окружения — если обсуждение касается архитектуры кода, речь идёт о Separation of Concerns. В этой статье SoC везде относится к принципу разделения ответственности.
Трёхслойная архитектура — самый распространённый способ реализации SoC в мобильных приложениях. Она делит код на Presentation (UI), Domain (бизнес-логика) и Data (работа с источниками). Каждый слой содержит строго определённые типы классов и изолирован от соседей через интерфейсы. Этот подход одинаково эффективен для iOS, Android и Flutter проектов.
View и ViewModel образуют presentation-слой. View отвечает за отрисовку интерфейса и передачу событий пользователя. ViewModel хранит состояние экрана и преобразует данные из Domain-слоя в формат, готовый для отображения. ViewModel не имеет ссылок на Activity, Fragment или UIViewController — это обеспечивает SoC между UI и логикой.
Например, в Android Jetpack ViewModel переживает поворот экрана, а UI пересоздаётся. Без SoC пришлось бы сохранять состояние в Activity, смешивая управление жизненным циклом с данными. ViewModel решает эту задачу изолированно, демонстрируя чистую реализацию принципа разделения ответственности.
Use Cases содержат бизнес-правила, не зависящие от платформы. Этот слой не импортирует Android SDK, iOS UIKit или Flutter framework. Use Case получает данные из Repository, применяет к ним бизнес-логику и возвращает результат. Благодаря SoC один Use Case можно переиспользовать на разных экранах и платформах.
Классический пример — ValidateAndSaveUseCase для формы регистрации. Он проверяет корректность email и пароля, вызывает UserRepository для сохранения и возвращает ValidationResult. Ни UI, ни база данных не знают о правилах валидации — они сосредоточены в одном месте, что упрощает их изменение.
Repository абстрагирует источники данных от остального приложения. ViewModel не знает, откуда берутся данные — из REST API, GraphQL, локальной базы данных или кэша. Repository решает, какой источник использовать, и скрывает эту логику за интерфейсом. Это SoC между получением данных и их потреблением.
DataSource обеспечивает ещё более глубокое разделение: RemoteDataSource отвечает только за HTTP-запросы, LocalDataSource — за работу с Room, CoreData или SharedPreferences. Repository комбинирует их, применяя стратегии кэширования. Каждый DataSource можно заменять независимо, что критически важно при миграции между серверами или базами данных.
Такая многоуровневая система DataSource реализует SoC на инфраструктурном уровне: сетевое взаимодействие, локальное хранение и кэширование — отдельные concerns, каждый со своей логикой и жизненным циклом. При замене HTTP-клиента меняется только RemoteDataSource, а Repository и вышестоящие слои остаются нетронутыми, что подтверждает практическую ценность разделения ответственности.
MVP (Model-View-Presenter) — один из первых паттернов, явно реализующих SoC в мобильной разработке. Presenter содержит логику и управляет View через интерфейс. View пассивна — она только отображает то, что говорит Presenter. Разделение упрощает тестирование: Presenter тестируется без эмулятора, а View остаётся настолько простой, что в ней нечему ломаться.
MVVM добавил реактивное связывание: View подписывается на изменения ViewModel через Observable или StateFlow. ViewModel не хранит ссылку на View, что устраняет риск утечки памяти и ещё сильнее разделяет concerns. В Android MVVM стал стандартом благодаря Jetpack ViewModel и LiveData, в iOS — благодаря Combine и RxSwift.
Clean Architecture Роберта Мартина доводит SoC до радикального разделения на кольца. Внешнее кольцо (фреймворки и драйверы) зависит от внутреннего (сущности), но не наоборот. На практике мобильные проекты редко реализуют все четыре кольца — достаточно Domain и Data слоёв вокруг Presentation. Но сам принцип зависимости «внутрь» даёт значимые преимущества при смене фреймворков.
// View — только отображение, без логики
final class LoginViewController: UIViewController {
let viewModel: LoginViewModel
func loginTapped() {
viewModel.login(emailField.text, passwordField.text)
}
}
// ViewModel — содержит логику экрана, не знает UIKit
final class LoginViewModel {
private let loginUseCase: LoginUseCase
func login(email: String?, password: String?) {
loginUseCase.execute(email, password)
}
}
// Use Case — бизнес-логика, не зависит от платформы
final class LoginUseCase {
private let repo: AuthRepository
func execute(email: String?, password: String?) {
guard let e = email, let p = password else { return }
repo.authenticate(e, p)
}
}
Пример показывает три уровня SoC: LoginViewController только передаёт события, LoginViewModel управляет состоянием, LoginUseCase содержит бизнес-правила. Каждый класс тестируется независимо, и замена UI-фреймворка не затрагивает Use Case.
Massive View Controller — самое частое нарушение SoC в iOS. Класс, который управляет UI, обрабатывает сетевые запросы, парсит JSON и сохраняет данные, нарушает принцип на всех уровнях. Решение — вынести каждую ответственность в отдельный компонент: NetworkingService, JSONParser, CoreDataStack, оставив ViewController только управление View.
В Android аналогичная проблема — God Activity или God Fragment. Одна активность, которая загружает данные, валидирует формы, показывает диалоги и обновляет UI. Лечится внедрением ViewModel и Repository, которые забирают на себя управление состоянием и данными. ViewModel также защищает от потери данных при повороте экрана.
Третье нарушение — смешивание платформенного и бизнес-кода. Например, размещение HTTP-запроса напрямую в SwiftUI View или Android Composable. Это делает код непереносимым и сложным для тестирования. Правильный подход — вынести запрос в Repository, который вызывается через Use Case, а View только подписывается на результат. Каждый элемент системы решает свою задачу и не выходит за её границы.
Часто задаваемые вопросы
Нет. SoC — более общий принцип разделения системы на области ответственности. SOLID — набор из пяти конкретных правил для объектно-ориентированного проектирования. Первый принцип SOLID (Single Responsibility) является частным случаем SoC на уровне одного класса.
Используйте правило одной причины для изменения (Single Responsibility). Если класс меняется из-за изменения UI, формата данных и бизнес-правил — SoC нарушен. Инструменты вроде ArchTest (Android) и StrictConcurrency (iOS) помогают выявить такие нарушения автоматически.
В теории дополнительные слои добавляют косвенные вызовы, но на практике влияние на производительность мобильного приложения пренебрежимо. Компилятор инлайнит многие вызовы, а JIT и AOT оптимизации устраняют накладные расходы. Поддерживаемость кода выигрывает гораздо больше, чем теряется на абстракциях.
Начните с экстракции сетевых запросов из UI в Repository. Затем вынесите бизнес-логику в Use Cases. Используйте dependency injection для связывания слоёв. Делайте изменения итеративно, покрывая новый код тестами — это гарантирует, что рефакторинг не сломает существующую функциональность.
В прототипах можно нарушать SoC ради скорости. Но если прототип переходит в продуктовую разработку, затраты на рефакторинг могут превысить выгоду от быстрого старта. Оптимально — соблюдать минимальное разделение (UI и данные) даже в прототипе, чтобы не переписывать всё с нуля при запуске.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также