Шаблоны проектирования и приёмы рефакторинга
Соблюдать принцип единственной ответственности позволяют несколько шаблонов проектирования и приёмов рефакторинга.
Выделение класса
Выделение класса — приём рефакторинга, при котором из большого класса с множеством слабо-связанных по смыслу полей и методов, выделяется один или несколько классов.
Смысл приёма в том, чтобы явно выделить назначение класса. Идеальный результат — получить класс, который можно описать одной фразой или даже одним словом.
В примере ниже до рефакторинга мы имеем класс 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)
}
}