старший разработчик в Епам
провожу технические интервью
увлекаюсь путешествиями, был в 30 странах
люблю функциональное программирование в умеренных дозах
twitter: @igor_dlinni
github: IgorKonovalov
Принципы SOLID обычно обсуждаются в рамках объектно ориентированных языков с классическим наследованием (C++, Java).
JavaScript - язык со слабой типизацией, кто то считает его обьектно - ориентированным, другой - функциональным языком, истина где то посередине.
В JS обычно не используются классы и длинные цепочки наследования, но принципы SOLID прекрасно применимы и без них
const createCircle = (radius) => ({
type: 'Circle',
// методы объекта
radius,
})
const myCircle = createCircle(20)Когда объект имеет несколько ответственностей, его изменение, связанное с одной из ответственностей, может побочно изменить логику, связанную с другой ответственностью. Разделение ответственностей делает код более устойчивым к изменениям.
const areaCalculator = (shapes) => ({
sum() {
// логика суммы
},
output() {
return `
<h1>
Сумма площадей объектов:
${this.sum()}
</h1>
`
},
})const shapes = [
circle(2),
square(5),
square(6)
]
const areas = areaCalculator(shapes)
console.log(areas.output())areaCalculator нарушает принцип единственной ответственности
Расчет суммы
Вывод результата
теперь, если пользователь захочет получить данные в другом формате, ему придется менять areaCalculator, что сломает логику тех мест, где он уже используется
const shapes = [
circle(2),
square(5),
square(6)
]
const areasSum = areaCalculator(shapes)
const output = sumCalculatorOutputter(areasSum)
console.log(output.JSON())
console.log(output.HAML())
console.log(output.HTML())
console.log(output.JADE())Вынеся вывод данных наружу areaCalculator мы сделали код более простым в понимании и легким для изменения и тестирования
архитектура приложения должна позволять менять поведение неких сущностей через расширение без изменения их исходного кода
sum() {
const area = []
for (shape of this.shapes) {
if (shape.type === 'Square') {
area.push(Math.pow(shape.length, 2)
} else if (shape.type === 'Circle') {
area.push(Math.PI * Math.pow(shape.length, 2)
}
}
return area.reduce((value, accumulator) => accumulator += value, 0)
}Взглянем на метод sum фабрики areaCalculator
Принцип открытости - закрытости нарушается, тк при введении новых типов объектов нам придется менять метод sum
const square = (length) => ({
type: 'Square',
area () {
return Math.pow(this.length, 2)
},
})вынесем метод area в фабрику объекта:
sum() {
const area = []
for (shape of this.shapes) {
area.push(shape.area())
}
return area.reduce((value, accumulator) => accumulator += value, 0)
}Наследующий класс должен дополнять, а не изменять базовый.
Пусть q (x) - свойство, доказуемое для объектов x типа T. Тогда q (y) должно быть доказуемым для объектов y типа S, где S - подтип T.
вместо наследования сущностей
Animal
Mammal
Domesticated
Cat
объединяйте свойства
теплый
бегатель
мяукатель
Cat
const runner = state => ({
run() {
state.position = state.position + state.speed
}
})
const mewer = state => ({
mew() {
console.log(`mrrr , I'm a ${state.name}, mrrrr`)
}
})
const cat = name => {
const state = {
name,
speed: 5,
position: 0,
}
return Object.assign(
{},
runner(state),
mewer(state),
// ...
)
}слишком «толстые» интерфейсы необходимо разделять на более маленькие и специфические, чтобы программные сущности маленьких интерфейсов знали только о методах, которые необходимы им в работе.
В итоге, при изменении метода интерфейса не должны меняться программные сущности, которые этот метод не используют
Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба должны зависеть от абстракции.
Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
import fetch from 'fetch'
const getPersonalData = params =>
fetch('url')
.then(response => response.json())
.then(data => data.map(user => user.data))import fetch from 'fetch'
const createGetPersonalData = fetcher => props => fetcher('url').then(...)
const getPersonalData = createGetPersonalData(fetch)функция getPersonalData зависит от fetch
функция createGetPersonalData зависит от абстракции fetcher