Одна причина для изменения
Принцип единственной ответственности — это буква S в SOLID, пяти правилах проектирования кода, который остаётся лёгким для изменения. Он гласит: у типа должна быть одна задача — и, следовательно, только одна причина для изменения.
Когда один класс сохраняет заказы и отправляет письма, он меняется и при изменении хранилища, и при изменении текста письма. Две задачи, две причины и вдвое больший риск сломать одно, редактируя другое.
В TypeScript это правило применимо к классам, их методам и модулям: пусть каждый из них занимается одной задачей.
Слишком много задач в одном типе
Этот обработчик заказов работает — но он сохраняет заказ, составляет текст подтверждения и «отправляет» его, и всё это в одном методе. Проверить отдельно текст письма невозможно, а чтобы переиспользовать часть с хранилищем в другом месте, придётся тащить за собой и код письма.
Один тип, который делает всё
process() смешивает две задачи: сохранение и уведомление. Любое изменение в любой из них означает правку — и повторное тестирование — этого же класса.
Вывод: программа на TypeScript и захваченный вывод
class OrderProcessor {
// process выполняет две задачи: сохранение И уведомление.
process(order: string, customer: string): void {
console.log("сохраняем заказ в хранилище:", order)
console.log("отправляем письмо на " + customer + ":")
console.log(" Спасибо! Ваш заказ", order, "принят.")
}
}
const processor = new OrderProcessor()
processor.process("#1042 (2 книги)", "[email protected]")
Одна задача на тип
Решение: один класс на одну задачу. OrderStore только сохраняет, EmailSender только отправляет. Небольшая функция связывает их.
Вывод тот же — но теперь каждую часть можно менять, тестировать и переиспользовать отдельно.
Разделение на два типа
То же поведение, но разделённое: хранилище ничего не знает о письмах, отправитель ничего не знает о хранении. Функция processOrder просто соединяет их.
Вывод: программа на TypeScript и захваченный вывод
// У OrderStore одна задача: хранение заказов.
class OrderStore {
save(order: string): void {
console.log("сохраняем заказ в хранилище:", order)
}
}
// У EmailSender одна задача: отправка писем.
class EmailSender {
sendConfirmation(customer: string, order: string): void {
console.log("отправляем письмо на " + customer + ":")
console.log(" Спасибо! Ваш заказ", order, "принят.")
}
}
function processOrder(order: string, customer: string): void {
const store = new OrderStore()
const sender = new EmailSender()
store.save(order)
sender.sendConfirmation(customer, order)
}
processOrder("#1042 (2 книги)", "[email protected]")
Выгода: переиспользование без страха
Поскольку отправитель — отдельный класс, он отправляет и уведомление об отправке — без всякого кода хранилища. А хранилище архивирует заказ, не составляя ни одного письма. Каждый класс вырос внутри своей единственной задачи.
Вывод: программа на TypeScript и захваченный вывод
class OrderStore {
save(order: string): void {
console.log("сохраняем заказ в хранилище:", order)
}
archive(order: string): void {
console.log("переносим заказ в архив:", order)
}
}
class EmailSender {
sendConfirmation(customer: string, order: string): void {
console.log("письмо на " + customer + ": заказ", order, "принят")
}
sendShippingNotice(customer: string, order: string): void {
console.log("письмо на " + customer + ": заказ", order, "отправлен")
}
}
const store = new OrderStore()
const sender = new EmailSender()
// Каждый класс растёт внутри своей задачи.
sender.sendShippingNotice("[email protected]", "#1042")
store.archive("#0997")