Комментарии (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
- Классы вынуждены реализовывать ненужные методы
- Сложнее тестировать — нужно создавать моки для ненужных методов
- Сложнее понять код — не ясно какие методы реально нужны
- Сложнее расширять — изменение интерфейса влияет на все реализации
Решение — разделение на маленькие интерфейсы
// ХОРОШО — следование 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 показывает глубокое понимание проектирования архитектуры и способность писать гибкий, расширяемый код.