SOLID

О себе

  • старший разработчик в Епам

  • провожу технические интервью

  • увлекаюсь путешествиями, был в 30 странах

  • люблю функциональное программирование в умеренных дозах

twitter: @igor_dlinni

github: IgorKonovalov

Что такое S.O.L.I.D. ?

Мнемонический акроним, придуманный Робертом Мартином в начале 2000х гг

  • Первое упоминание - статья Design Principles and Design Patterns (1 января 2000г)
  • Позволяет писать расширяемый и поддерживаемый код, избегая стандартных проблем с которыми сталкивается приложение в ходе своего жизненного цикла
  • Часть общей стратегии гибкой и адаптивной разработки

Фактически любое приложение, которое вы пишите сегодня - будет изменяться

Как вы справитесь с этим? 

Закрепощённость

Каждое изменение вызывает каскад связанных изменений

Неустойчивость

Ломается непосредственно не связанный с изменениями код

Неподвижность

Код жестко связан, переиспользовать что-либо невозможно

Вязкость

Продолжать писать плохой код - наиболее привлекательная альтернатива

Когда вы только начинаете писать приложение - все кажется другим..

Ваше приложение - идеально

Оно легко расширяемо, понятно, код легко читается

Вам кажется, что так будет всегда - но со временем все меняется...

Зависимости убивают ваше приложение

Дизайн может спасти вас

Гипотеза "выносливости" дизайна

Зависимости:

Если ваш код использует какой то внешний ресурс

он зависит от него

Как только внешний ресурс меняется 

вашему коду тоже нужно меняться

Чтоб избежать зависимостей, ваш код должен быть

  • слабо связанным

  • однородным

  • легко композируемым

  • независимым от контекста

S

The Single Responsibility Principle

Принцип единственной ответственности

The Open Closed Principle

Принцип открытости/закрытости

The Liskov Substitution Principle

Принцип подстановки Барбары Лисков

The Interface Segregation Principle

Принцип разделения интерфейсов

The Dependency Inversion Principle

Принцип обращения зависимостей

D

I

L

O

SOLID в контексте JS

Принципы 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),
    // ...
  )
}

Принцип разделения интерфейса

"много интерфейсов, специально предназначенных для клиентов, лучше, чем один интерфейс общего назначения"

слишком «толстые» интерфейсы необходимо разделять на более маленькие и специфические, чтобы программные сущности маленьких интерфейсов знали только о методах, которые необходимы им в работе. 

В итоге, при изменении метода интерфейса не должны меняться программные сущности, которые этот метод не используют

решается сочетанием S, L и D

Принцип инверсии (обращения) зависимостей

"Зависимость на Абстракциях. Нет зависимости на что-то конкретное"

  • Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба должны зависеть от абстракции.

  • Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

Принцип инверсии зависимостей

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

Подводные камни

SOLID

YAGNI

KISS

Полезные ссылки

Конец!

Made with Slides.com