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

Что такое принцип единственной ответственности (SRP, Single Responsibility Principle) в ООП?

2.0 Middle🔥 162 комментариев
#Архитектура и паттерны

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

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

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

Что такое принцип единственной ответственности (SRP)

Принцип единственной ответственности (Single Responsibility Principle, SRP) — это первый из пяти принципов SOLID в объектно-ориентированном программировании. Он был сформулирован Робертом Мартином и гласит: "Каждый класс (или модуль) должен иметь одну и только одну причину для изменения". Другими словами, у класса должна быть одна ответственность — одна область поведения, за которую он отвечает.

Суть принципа на практике

В контексте разработки под Android SRP означает, что:

  • Activity/Fragment должны заниматься в первую очередь управлением жизненным циклом UI и обработкой пользовательского ввода, а не сетевыми запросами, работой с базой данных или сложной бизнес-логикой.
  • Класс для работы с сетью должен отвечать только за выполнение HTTP-запросов и их базовую обработку, а не за кэширование или преобразование данных.
  • Класс для работы с БД (Room DAO) должен отвечать только за операции с базой данных.

Пример нарушения SRP:

Рассмотрим типичный Activity, который делает "всё и сразу". Это антипаттерн "God Object".

// ПЛОХО: Класс нарушает SRP, взяв на себя множество обязанностей.
class UserProfileActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_user_profile)

        loadUserData()
    }

    private fun loadUserData() {
        // 1. Ответственность: Сетевой запрос
        val call = RetrofitClient.apiService.getUserProfile()
        call.enqueue(object : Callback<UserResponse> {
            override fun onResponse(call: Call<UserResponse>, response: Response<UserResponse>) {
                if (response.isSuccessful) {
                    val user = response.body()
                    // 2. Ответственность: Преобразование данных (маппинг DTO -> Model)
                    val domainUser = user?.let { mapToDomain(it) }
                    // 3. Ответственность: Сохранение в БД
                    saveToDatabase(domainUser)
                    // 4. Ответственность: Обновление UI
                    runOnUiThread { updateUI(domainUser) }
                }
            }
            override fun onFailure(call: Call<UserResponse>, t: Throwable) {
                // 5. Ответственность: Обработка ошибок сети
                showNetworkError(t.message)
            }
        })
    }

    private fun mapToDomain(response: UserResponse): User { /* ... */ }
    private fun saveToDatabase(user: User?) { /* ... */ }
    private fun updateUI(user: User?) { /* ... */ }
    private fun showNetworkError(message: String?) { /* ... */ }
}

Проблемы такого подхода:

  • Тестирование: Невозможно протестировать бизнес-логику изолированно от Android-компонентов.
  • Поддержка: Изменение формата API, логики маппинга или способа кэширования потребует изменений в Activity.
  • Переиспользование: Код сетевого запроса или маппинга нельзя использовать в другом месте.
  • Читаемость: Класс раздувается и его сложно понять.

Рефакторинг с соблюдением SRP

Давайте разделим ответственности, выделив отдельные классы.

// ХОРОШО: Каждый класс имеет одну четкую ответственность.

// 1. Ответственность: Работа с сетью (получение данных)
class UserRemoteDataSource(private val apiService: ApiService) {
    suspend fun getUserProfile(): UserResponse {
        return apiService.getUserProfile()
    }
}

// 2. Ответственность: Преобразование данных (маппинг слоев)
class UserMapper {
    fun mapToDomain(response: UserResponse): User {
        return User(
            id = response.id,
            name = "${response.firstName} ${response.lastName}",
            email = response.email
        )
    }
}

// 3. Ответственность: Работа с локальным хранилищем (БД)
class UserLocalDataSource(private val userDao: UserDao) {
    suspend fun saveUser(user: User) {
        userDao.insertUser(user)
    }
}

// 4. Ответственность: Выполнение бизнес-логики (Use Case/Interactor)
class GetUserProfileUseCase(
    private val remoteDataSource: UserRemoteDataSource,
    private val localDataSource: UserLocalDataSource,
    private val mapper: UserMapper
) {
    suspend fun execute(): Result<User> {
        return try {
            val response = remoteDataSource.getUserProfile()
            val domainUser = mapper.mapToDomain(response)
            localDataSource.saveUser(domainUser)
            Result.success(domainUser)
        } catch (e: Exception) {
            Result.failure(e)
        }
    }
}

// 5. Ответственность Activity: Управление UI и жизненным циклом
class UserProfileActivity : AppCompatActivity() {

    private lateinit var getUserProfileUseCase: GetUserProfileUseCase
    private val viewModel: UserProfileViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_user_profile)

        observeViewModel()
        viewModel.loadUserProfile()
    }

    private fun observeViewModel() {
        viewModel.userProfileState.observe(this) { state ->
            when (state) {
                is Result.Success -> updateUI(state.data)
                is Result.Failure -> showError(state.exception.message)
            }
        }
    }
    private fun updateUI(user: User) { /* ... */ }
    private fun showError(message: String?) { /* ... */ }
}

Преимущества соблюдения SRP в Android-разработке

  • Упрощение тестирования: Классы вроде GetUserProfileUseCase или UserMapper легко тестируются юнит-тестами без необходимости эмуляции Android-окружения.
  • Повышение сопровождаемости: Изменения в одной области (например, в API) затрагивают минимальное количество классов.
  • Увеличение переиспользуемости: Классы данных, мапперы, источники данных можно легко использовать в разных частях приложения.
  • Улучшение читаемости: Код становится более структурированным и понятным, каждый класс имеет четкое назначение.
  • Снижение связанности (coupling): Классы зависят от абстракций (интерфейсов), а не от конкретных реализаций, что делает архитектуру гибче.

Таким образом, SRP — это фундаментальный принцип, который ведет к созданию чистого, модульного и устойчивого к изменениям кода. На Android его соблюдение напрямую связано с успешным применением современных архитектурных подходов, таких как Clean Architecture или MVVM, где разделение ответственности между слоями (UI, Domain, Data) является ключевым.

Что такое принцип единственной ответственности (SRP, Single Responsibility Principle) в ООП? | PrepBro