Шаблоны проектирования и приёмы рефакторинга

Соблюдать принцип единственной ответственности позволяют несколько шаблонов проектирования и приёмов рефакторинга.

Выделение класса

Выделение класса — приём рефакторинга, при котором из большого класса с множеством слабо-связанных по смыслу полей и методов, выделяется один или несколько классов.

Смысл приёма в том, чтобы явно выделить назначение класса. Идеальный результат — получить класс, который можно описать одной фразой или даже одним словом.

В примере ниже до рефакторинга мы имеем класс 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)
  }
}

Вопросы

Материалы к разделу