В реальной жизни
ISP можно рассматривать как принцип единой ответственности (SRP) для интерфейсов. Его главная задача — помочь спроектировать интерфейсы так, чтобы в них не было ничего лишнего. Принцип помогает выявлять более высокие абстракции и находить неочевидные связи между сущностями.
Траты и доходы в Койне
Приложение Койн — это приложение для экономии, будущий преемник Тяжеловато и площадка для экспериментов.
В Койне есть возможность записывать траты и доходы. Разработчики использовали ISP, чтобы сделать работу с этими двумя сущностями более удобной.
Сперва в приложении были только траты, поэтому в истории хранились только объекты типа Spend
.
interface Spend {
amount: number
date: Timestamp
type: 'helpful' | 'harmful' | null
}
type History = Spend[]
Когда в приложении добавились доходы, в истории стали храниться не только траты. У трат и доходов были общие поля, поэтому было логично вынести их в общий интерфейс Record
, а интерфейс траты расширить от него:
type RecordTypes = 'spend' | 'income'
type SpendTypes = 'helpful' | 'harmful' | null
// общие поля описаны в общем интерфейсе;
// ISP помогает выявить скрытые связи между сущностями
// и зависимости в их поведении
interface Record {
amount: number
date: Timestamp
is: RecordTypes
// поля type здесь уже нет,
// для доходов оно не нужно
}
// интерфейс траты расширяет интерфейс записи
interface Spend extends Record {
type: SpendTypes
}
Траты и доходы стали реализовать эти интерфейсы так, чтобы базовая запись описывала общее поведение, а трата и доход — только специфичное для них:
class RecordItem implements Record {
amount: number
date: Timestamp
is: RecordTypes
constructor(amount: number) {
this.amount = amount
this.date = Date.now()
}
}
class SpendItem extends RecordItem implements Spend {
type: SpendTypes
constructor(amount: number, type: SpendTypes = null) {
super(amount)
this.is = 'spend'
this.type = type
}
}
class IncomeItem extends RecordItem {
constructor(amount: number) {
super(amount)
this.is = 'income'
}
}
Так функциональность не дублируется, но классы при этом не зависят от методов, которые им не нужны.
Уведомления в Задачнике
Задачник — это система управления задачами и учёта времени работы.
Для напоминания о подходящих сроках задачи в Задачнике используются уведомления. Уведомления бывают разных типов: push, SMS и почтовые.
Согласно ISP, общие для всех типов поля и методы разработчики хранят в общем интерфейсе Message
:
interface Message {
title: string
body: string
send(to: string[]): void
}
Детали же — описывают конкретно под каждый тип уведомлений:
interface SmsMessage extends Message {
smsService: SmsService
// ...
}
interface PushMessage extends Message {
pushService: PushService
// ...
}
interface EmailMessage extends Message {
emailService: EmailService
// ...
}
Результат такой же, как в предыдущем примере: функциональность не дублируется, классы реализуют только от те методы, которые им нужны.