В идеальном мире

В идеальном мире каждый класс в коде решает одну и только одну задачу, а все задачи структурированы и разделены. Модули в этом случае дополняют друг друга, а их совокупность детально описывает систему.

Допустим, у нас есть задача создать отчёт об активности пользователей и вывести его в нескольких вариантах: как строку 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 = new FormatSelector(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 {
  formatters = {
    [ReportTypes.Html]: HtmlFormatter,
    [ReportTypes.Txt]: TxtFormatter,
  }

  constructor(reportType: ReportTypes) {
    const FormatterFactory = this.formatters[reportType]
    return new FormatterFactory()
  }
}

const dynamicFormatter = new FormatSelector(ReportTypes.Html)
dynamicFormatter.format(/*...*/)

Таким образом SRP помогает разделить ответственность за различные задачи между сущностями и сделать это так, чтобы каждая сущность занималась одной задачей.

Вопросы