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

Можешь привести пример использования принципа S в SOLID

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

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

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

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

Пример использования Принципа единственной ответственности (SRP) в Android-разработке

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

❌ Проблема: Класс с множеством ответственностей

Допустим, у нас есть класс UserManager, который выполняет слишком много действий:

// НЕПРАВИЛЬНО: Нарушение SRP - класс делает ВСЁ
class UserManager(private val context: Context) {
    
    // 1. Работа с SharedPreferences
    fun saveUserToPrefs(user: User) {
        val prefs = context.getSharedPreferences("user_prefs", Context.MODE_PRIVATE)
        prefs.edit()
            .putString("name", user.name)
            .putInt("age", user.age)
            .putString("email", user.email)
            .apply()
    }
    
    // 2. Работа с базой данных
    fun saveUserToDatabase(user: User) {
        val db = AppDatabase.getInstance(context)
        db.userDao().insert(user)
    }
    
    // 3. Сетевая логика
    fun uploadUserToServer(user: User) {
        val apiService = RetrofitClient.getApiService()
        apiService.uploadUser(user).enqueue(object : Callback<Void> {
            override fun onResponse(call: Call<Void>, response: Response<Void>) {
                // Обработка ответа
            }
            override fun onFailure(call: Call<Void>, t: Throwable) {
                // Обработка ошибки
            }
        })
    }
    
    // 4. Валидация данных
    fun validateUser(user: User): Boolean {
        return user.name.isNotEmpty() && 
               user.email.contains("@") && 
               user.age > 0
    }
    
    // 5. UI-логика
    fun showUserDetailsDialog(user: User) {
        AlertDialog.Builder(context)
            .setTitle("User Info")
            .setMessage("Name: ${user.name}\nEmail: ${user.email}")
            .setPositiveButton("OK", null)
            .show()
    }
}

data class User(val name: String, val email: String, val age: Int)

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

  • Сложность тестирования: Для юнит-теста нужно мокать Context, базу данных, сетевые запросы и UI.
  • Сложность поддержки: Любое изменение в работе с SharedPreferences, базой данных или сетью приведет к изменению этого класса.
  • Высокая связность: Все компоненты тесно переплетены. Изменение одной функции может сломать другую.
  • Сложность повторного использования: Нельзя переиспользовать логику валидации или сохранения отдельно от UI-логики.

✅ Решение: Разделение ответственности

Разделим UserManager на несколько классов, каждый со своей единственной задачей:

// 1. Класс для работы с SharedPreferences
class UserPreferencesManager(private val context: Context) {
    private val prefs = context.getSharedPreferences("user_prefs", Context.MODE_PRIVATE)
    
    fun saveUser(user: User) {
        prefs.edit()
            .putString("name", user.name)
            .putInt("age", user.age)
            .putString("email", user.email)
            .apply()
    }
    
    fun getUser(): User? {
        return User(
            name = prefs.getString("name", "") ?: "",
            email = prefs.getString("email", "") ?: "",
            age = prefs.getInt("age", 0)
        )
    }
}

// 2. Класс для работы с базой данных (используя Room)
class UserRepository(private val userDao: UserDao) {
    suspend fun saveUserToDb(user: User) {
        userDao.insert(user)
    }
    
    suspend fun getUserFromDb(userId: Int): User? {
        return userDao.getUserById(userId)
    }
}

// 3. Класс для сетевых операций
class UserApiService(private val apiService: ApiService) {
    suspend fun uploadUser(user: User): Result<Void> {
        return try {
            val response = apiService.uploadUser(user)
            if (response.isSuccessful) Result.success(response.body()!!)
            else Result.failure(Exception("Server error"))
        } catch (e: Exception) {
            Result.failure(e)
        }
    }
}

// 4. Класс для валидации
class UserValidator {
    fun validate(user: User): ValidationResult {
        return when {
            user.name.isBlank() -> ValidationResult.Error("Name is empty")
            !user.email.contains("@") -> ValidationResult.Error("Invalid email")
            user.age <= 0 -> ValidationResult.Error("Invalid age")
            else -> ValidationResult.Success
        }
    }
    
    sealed class ValidationResult {
        object Success : ValidationResult()
        data class Error(val message: String) : ValidationResult()
    }
}

// 5. Класс для UI-логики (например, во ViewModel или Presenter)
class UserViewModel(
    private val preferencesManager: UserPreferencesManager,
    private val validator: UserValidator,
    private val repository: UserRepository
) : ViewModel() {
    
    fun saveUser(user: User) {
        // Валидация
        when (val result = validator.validate(user)) {
            is UserValidator.ValidationResult.Error -> {
                // Показать ошибку в UI
                _errorMessage.value = result.message
                return
            }
            is UserValidator.ValidationResult.Success -> {
                // Сохранение в разных источниках
                preferencesManager.saveUser(user)
                viewModelScope.launch {
                    repository.saveUserToDb(user)
                }
            }
        }
    }
}

Преимущества применения SRP:

  • Упрощение тестирования: Каждый класс теперь можно тестировать изолированно:

    class UserValidatorTest {
        @Test
        fun `validate returns error for empty name`() {
            val validator = UserValidator()
            val user = User("", "test@mail.com", 25)
            val result = validator.validate(user)
            assertTrue(result is UserValidator.ValidationResult.Error)
        }
    }
    
  • Простота поддержки: Если изменится логика валидации, правим только UserValidator. Изменение структуры SharedPreferences затрагивает только UserPreferencesManager.

  • Снижение связанности: Классы теперь зависят от абстракций (интерфейсов), а не от конкретных реализаций.

  • Повторное использование: UserValidator можно использовать в разных частях приложения, а UserApiService - в разных ViewModel.

  • Читаемость кода: Каждый класс имеет понятное назначение, что упрощает чтение и понимание кода новыми разработчиками.

На практике в Android-разработке SRP особенно важен при проектировании ViewModel, Repository, UseCase и других компонентов чистой архитектуры. Применение этого принципа делает код более гибким, масштабируемым и устойчивым к изменениям, что критически важно для долгосрочной поддержки мобильного приложения.

Можешь привести пример использования принципа S в SOLID | PrepBro