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

Что такое I в SOLID?

1.0 Junior🔥 131 комментариев
#Архитектура и паттерны

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

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

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

Interface Segregation Principle (ISP) — I в SOLID

ISP (Принцип разделения интерфейса) — это один из пяти принципов SOLID, который гласит: "Клиент не должен зависеть от интерфейсов, которые он не использует". Проще: делай маленькие специализированные интерфейсы, а не большие универсальные.

Основная идея

Много узких интерфейсов лучше чем один толстый интерфейс.

// ПЛОХО — нарушение ISP
interface Worker {
    fun work()
    fun eat()
    fun sleep()
    fun manage()
    fun code()
    fun design()
    fun test()
}

class Manager : Worker {
    override fun work() { }
    override fun eat() { }
    override fun sleep() { }
    override fun manage() { }  // Manager делает это
    override fun code() { }     // Manager НЕ делает
    override fun design() { }   // Manager НЕ делает
    override fun test() { }     // Manager НЕ делает
}

class Developer : Worker {
    override fun work() { }
    override fun eat() { }
    override fun sleep() { }
    override fun manage() { }   // Developer НЕ делает
    override fun code() { }     // Developer делает это
    override fun design() { }   // Developer может делать
    override fun test() { }     // Developer может делать
}

Проблемы нарушения ISP

  1. Классы вынуждены реализовывать ненужные методы
  2. Сложнее тестировать — нужно создавать моки для ненужных методов
  3. Сложнее понять код — не ясно какие методы реально нужны
  4. Сложнее расширять — изменение интерфейса влияет на все реализации

Решение — разделение на маленькие интерфейсы

// ХОРОШО — следование ISP

// Базовые интерфейсы
interface Worker {
    fun work()
}

interface Eater {
    fun eat()
}

interface Sleeper {
    fun sleep()
}

// Специализированные интерфейсы
interface Manager : Worker, Eater, Sleeper {
    fun manage()
}

interface Developer : Worker, Eater, Sleeper {
    fun code()
}

interface Tester : Worker, Eater, Sleeper {
    fun test()
}

interface Designer : Worker, Eater, Sleeper {
    fun design()
}

// Реализация
class SoftwareManager : Manager {
    override fun work() { println("Managing team") }
    override fun manage() { println("Assigning tasks") }
    override fun eat() { println("Eating lunch") }
    override fun sleep() { println("Sleeping") }
}

class QAEngineer : Tester {
    override fun work() { println("Testing code") }
    override fun test() { println("Running tests") }
    override fun eat() { println("Eating") }
    override fun sleep() { println("Sleeping") }
}

Практические примеры в Android

1. Repository паттерн

// ПЛОХО — один большой интерфейс
interface UserRepository {
    suspend fun getUsers(): List<User>
    suspend fun getUserById(id: String): User?
    suspend fun createUser(user: User): User
    suspend fun updateUser(user: User): User
    suspend fun deleteUser(id: String)
    suspend fun searchUsers(query: String): List<User>
    suspend fun filterByAge(age: Int): List<User>
    suspend fun filterByLocation(location: String): List<User>
    suspend fun blockUser(id: String)
    suspend fun reportUser(id: String, reason: String)
    // ... еще много методов
}

// Класс который читает только пользователя
class UserDetailViewModel(val repository: UserRepository) {
    // Зависит от всех методов, хотя использует только getUserById
    fun loadUser(id: String) {
        val user = repository.getUserById(id)  // Это все что нужно!
    }
}

ХОРОШО — разделенные интерфейсы

// Узкие, специализированные интерфейсы
interface UserReader {
    suspend fun getUsers(): List<User>
    suspend fun getUserById(id: String): User?
    suspend fun searchUsers(query: String): List<User>
}

interface UserWriter {
    suspend fun createUser(user: User): User
    suspend fun updateUser(user: User): User
    suspend fun deleteUser(id: String)
}

interface UserFilter {
    suspend fun filterByAge(age: Int): List<User>
    suspend fun filterByLocation(location: String): List<User>
}

interface UserModerator {
    suspend fun blockUser(id: String)
    suspend fun reportUser(id: String, reason: String)
}

// Реализация
class UserRepositoryImpl(
    private val api: UserApi,
    private val database: UserDatabase
) : UserReader, UserWriter, UserFilter, UserModerator {
    // реализация
}

// Использование — зависимость от нужного интерфейса
class UserDetailViewModel(val reader: UserReader) {
    fun loadUser(id: String) {
        // reader имеет только необходимые методы
        val user = reader.getUserById(id)
    }
}

class UserListViewModel(val reader: UserReader, val filter: UserFilter) {
    fun loadUsers() {
        val users = reader.getUsers()
    }
    
    fun filterByAge(age: Int) {
        val filtered = filter.filterByAge(age)
    }
}

class UserModeratorPanel(val moderator: UserModerator) {
    fun blockUser(id: String) {
        moderator.blockUser(id)
    }
}

2. API Client

// ПЛОХО
interface ApiClient {
    suspend fun get(url: String): Response
    suspend fun post(url: String, body: String): Response
    suspend fun put(url: String, body: String): Response
    suspend fun delete(url: String): Response
    fun addInterceptor(interceptor: Interceptor)
    fun setTimeout(ms: Long)
    fun setRetries(count: Int)
    fun setBaseUrl(url: String)
    // и много других
}

// ХОРОШО
interface HttpClient {
    suspend fun get(url: String): Response
    suspend fun post(url: String, body: String): Response
}

interface HttpClientConfig {
    fun addInterceptor(interceptor: Interceptor)
    fun setTimeout(ms: Long)
    fun setRetries(count: Int)
}

class OkHttpClient : HttpClient, HttpClientConfig {
    // реализация
}

// Использование
class UserApi(val httpClient: HttpClient) {
    // httpClient имеет только нужные методы get/post
    suspend fun getUser(id: String) = httpClient.get("/users/$id")
}

3. UI компоненты

// ПЛОХО — один большой интерфейс
interface UserView {
    fun showUsers(users: List<User>)
    fun showLoading()
    fun hideLoading()
    fun showError(message: String)
    fun navigateToDetail(user: User)
    fun showDeleteConfirmation(user: User)
    fun updateUser(user: User)
    fun showNetworkError()
    fun showDataError()
    fun showSuccessMessage(message: String)
    // много методов
}

class UserListActivity : UserView {
    override fun showUsers(users: List<User>) { }
    override fun showLoading() { }
    override fun hideLoading() { }
    override fun showError(message: String) { }
    override fun navigateToDetail(user: User) { }  // Использует это
    override fun showDeleteConfirmation(user: User) { }  // Не использует
    override fun updateUser(user: User) { }  // Не использует
    override fun showNetworkError() { }
    override fun showDataError() { }
    override fun showSuccessMessage(message: String) { }  // Не использует
}

// ХОРОШО — разделенные интерфейсы
interface LoadingView {
    fun showLoading()
    fun hideLoading()
}

interface ErrorView {
    fun showError(message: String)
    fun showNetworkError()
    fun showDataError()
}

interface UserListView {
    fun showUsers(users: List<User>)
    fun navigateToDetail(user: User)
}

interface MessageView {
    fun showSuccessMessage(message: String)
}

class UserListActivity : UserListView, LoadingView, ErrorView, MessageView {
    override fun showUsers(users: List<User>) { }
    override fun navigateToDetail(user: User) { }
    override fun showLoading() { }
    override fun hideLoading() { }
    override fun showError(message: String) { }
    override fun showNetworkError() { }
    override fun showDataError() { }
    override fun showSuccessMessage(message: String) { }
}

4. Service интерфейсы

// ПЛОХО
interface NotificationService {
    fun sendEmail(to: String, subject: String, body: String)
    fun sendSMS(phone: String, message: String)
    fun sendPushNotification(userId: String, message: String)
    fun sendSlackMessage(channel: String, message: String)
    fun logToAnalytics(event: String, params: Map<String, Any>)
}

// ХОРОШО
interface EmailSender {
    fun send(to: String, subject: String, body: String)
}

interface SMSSender {
    fun send(phone: String, message: String)
}

interface PushSender {
    fun send(userId: String, message: String)
}

interface SlackSender {
    fun send(channel: String, message: String)
}

interface AnalyticsLogger {
    fun log(event: String, params: Map<String, Any>)
}

// Использование — каждый класс зависит только от нужного интерфейса
class RegistrationService(
    val emailSender: EmailSender,
    val analyticsLogger: AnalyticsLogger
) {
    fun registerUser(email: String) {
        // emailSender and analyticsLogger это все что нужно
    }
}

Правила ISP

// 1. Клиент не должен знать о методах которые он не использует
// ❌ Плохо
class Task(val service: BigService) {
    fun execute() {
        service.methodIUse()  // Зависит от BigService с 10 методами
    }
}

// ✅ Хорошо
interface MyInterface {
    fun methodIUse()
}

class Task(val service: MyInterface) {
    fun execute() {
        service.methodIUse()  // Зависит только от нужного
    }
}

// 2. Интерфейс должен быть специализирован под роль
// ❌ Плохо — интерфейс не имеет единой роли
interface General {
    fun read()
    fun write()
    fun delete()
    fun manage()
    fun report()
}

// ✅ Хорошо — каждый интерфейс имеет одну роль
interface Reader { fun read() }
interface Writer { fun write() }
interface Deleter { fun delete() }
interface Manager { fun manage() }
interface Reporter { fun report() }

Тестирование с ISP

// ISP облегчает тестирование — меньше методов моковать
class UserDetailViewModelTest {
    @Test
    fun testLoadUser() = runTest {
        // Мокируем только нужный интерфейс
        val mockReader = mockk<UserReader>()
        coEvery { mockReader.getUserById("123") } returns 
            User("123", "John")
        
        val viewModel = UserDetailViewModel(mockReader)
        viewModel.loadUser("123")
        
        // Проверяем что вызвалось только нужное
        coVerify(exactly = 1) { mockReader.getUserById("123") }
    }
}

Итог

ISP гласит:

  • Много узких интерфейсов лучше одного толстого
  • Клиент не должен зависеть от методов которые он не использует
  • Каждый интерфейс должен иметь одну роль

Преимущества следования ISP:

  • Слабая связанность (loose coupling)
  • Легче тестировать
  • Легче добавлять новые реализации
  • Код более понятный и поддерживаемый
  • Меньше side effects при изменении интерфейса

ISP показывает глубокое понимание проектирования архитектуры и способность писать гибкий, расширяемый код.

Что такое I в SOLID? | PrepBro