Шаблоны проектирования и приёмы рефакторинга
Соблюдать принцип единственной ответственности позволяют несколько шаблонов проектирования и приёмов рефакторинга.
Выделение класса
Выделение класса — приём рефакторинга, при котором из большого класса с множеством слабо-связанных по смыслу полей и методов, выделяется один или несколько классов.
Смысл приёма в том, чтобы явно выделить назначение класса. Идеальный результат — получить класс, который можно описать одной фразой или даже одним словом.
В примере ниже до рефакторинга мы имеем класс Person, который содержит логику преобразования телефонного номера. После — эта функциональность вынесена в класс PhoneNumber.
// До рефакторинга:
class Person {
  name: string
  phone: string
  officeCode: string
  constructor(name: string, phone: string, officeCode: string) {
    this.name = name
    this.phone = phone
    this.officeCode = officeCode
  }
  phoneNumberOf(): string {
    return `${this.phone} доб. ${this.officeCode}`
  }
}
// После:
interface IPhoneNumber {
  phone: string
  officeCode: string
  valueOf(): string
}
class PhoneNumber implements IPhoneNumber {
  phone: string
  officeCode: string
  constructor(phone: string, officeCode: string) {
    this.phone = phone
    this.officeCode = officeCode
  }
  valueOf(): string {
    return `${this.phone} доб. ${this.officeCode}`
  }
}
class Person {
  name: string
  phoneNumber: IPhoneNumber
  constructor(name: string, phoneNumber: IPhoneNumber) {
    this.name = name
    this.phoneNumber = phoneNumber
  }
  phoneNumberOf(): string {
    return this.phoneNumber.valueOf()
  }
}
Класс Person теперь работает только с данными пользователя, а задача преобразования номера делегируется экземпляру класса PhoneNumber через зависимость в конструкторе.
Вопросы
Фасад
Фасад — шаблон проектирования, при котором сложная логика скрывается за вызовом более простого API.
Фасад обеспечивает простое общение со сложной частью системы, беря ответственность за настройку и вызов специфических методов конкретных объектов на себя.
Один из минусов фасада в том, что он может превратиться в божественный объект.
В примере ниже мы выносим инициализацию и настройки классов Square и Circle в фасад ShapeFacade. После этого мы можем вызывать метод .areaOf фасада и получать площадь любой фигуры, которая подготовлена внутри фасада.
class Square extends Figure {
  length: number
  constructor(length: number) {
    this.length = length
  }
  areaOf(): number {
    return this.length ** 2
  }
}
class Circle extends Figure {
  radius: number
  constructor(radius: number) {
    this.radius = radius
  }
  areaOf(): number {
    return Math.PI * (this.radius ** 2)
  }
}
// Применение «Фасада»:
class ShapeFacade {
  square: Square
  circle: Circle
  constructor() {
    this.square = new Square(42)
    this.circle = new Circle(42)
  }
  areaOf(figure: string): number {
    switch (figure) {
      case 'square': return this.square.areaOf()
      case 'circle': return this.circle.areaOf()
      default: return 0
    }
  }
}
Вопросы
Прокси
Прокси — шаблон проектирования, при котором общение с каким-то объектом контролирует другой объект-заместитель (прокси). Он позволяет расширять функциональность существующих классов, не меняя их.
В примере мы используем прокси LoggedRequest, чтобы не примешивать логирование в класс, который реализует запросы к серверу RequestClient.
class RequestClient {
  async request(url: string): Promise<any> {
    try {
      const response = await fetch(url)
      const data = await response.json()
      return data
    }
    catch (e) {
      return null
    }
  }
}
class LoggedRequest {
  client: RequestClient
  constructor(client: RequestClient) {
    this.client = client
  }
  async request(url: string): Promise<any> {
    console.log(`Performed request to ${url}`)
    return await this.client.request(url)
  }
}