В идеальном мире
В идеальном мире каждый класс в коде решает одну и только одну задачу, а все задачи структурированы и разделены. Модули в этом случае дополняют друг друга, а их совокупность детально описывает систему.
Допустим, у нас есть задача создать отчёт об активности пользователей и вывести его в нескольких вариантах: как строку HTML или TXT.
Отчёт
Мы создадим класс ReportExporter
, который будет заниматься только экспортом данных. Определять необходимый формат будет класс FormatSelector
. А форматированием данных будут заниматься классы: HtmlFormatter
и TxtFormatter
.
// тип данных для отчёта
type ReportData = {
content: string,
date: Date,
size: number,
}
// возможные форматы
enum ReportTypes {
Html,
Txt,
}
// класс, который занимается экспортом данных
class ReportExporter {
name: string
data: ReportData
constructor(name: string, data: ReportData) {
this.name = name
this.data = data
}
export(reportType: ReportTypes): string {
const formatter: Formatter = FormatSelector.selectFor(reportType)
return formatter.format(this.data)
}
}
Форматы экспорта
В соответствии с SRP форматирование данных — это отдельная задача. Поэтому для преобразования данных отчёта в необходимый формат мы создадим отдельные классы.
interface Formatter {
format(data: ReportData): string
}
// класс для форматирования в HTML
class HtmlFormatter implements Formatter {
format(data: ReportData): string {
// форматируем данные в HTML и возвращаем:
return 'html string'
}
}
// класс для форматирования в TXT
class TxtFormatter implements Formatter {
format(data: ReportData): string {
// форматируем данные в TXT и возвращаем:
return 'txt string'
}
}
Выбор формата
Принцип единственной ответственности подсказывает, что выбор формата не входит ни в задачу форматирования данных, ни в задачу их подготовки. Поэтому существующие классы нам не подойдут.
Для решения этой задачи воспользуемся шаблоном проектирования «Стратегия», который поможет выбрать подходящий формат. (Более подробно «Стратегию» мы разберём в разделе о принципе открытости и закрытости.)
Создадим новый класс FormatSelector
, который будет выбирать тип форматирования, в зависимости от настроек.
class FormatSelector {
private static formatters = {
[ReportTypes.Html]: HtmlFormatter,
[ReportTypes.Txt]: TxtFormatter,
}
static selectFor(reportType: ReportTypes) {
const FormatterFactory = FormatSelector.formatters[reportType]
return new FormatterFactory();
}
}
const dynamicFormatter = FormatSelector.selectFor(ReportTypes.Html)
dynamicFormatter.format(/*...*/)
Таким образом SRP помогает разделить ответственность за различные задачи между сущностями и сделать это так, чтобы каждая сущность занималась одной задачей.