Можешь привести пример использования принципа S в SOLID
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Пример использования Принципа единственной ответственности (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 и других компонентов чистой архитектуры. Применение этого принципа делает код более гибким, масштабируемым и устойчивым к изменениям, что критически важно для долгосрочной поддержки мобильного приложения.