-
Notifications
You must be signed in to change notification settings - Fork 0
SOLID
Далее речь будет идти о таких сущностях в ООП, как классы, модули или объекты
Проектирование большого и сложного приложения начинается с разработки его архитектуры
Мы не можем написать такое приложение одним огромным и запутанным файлом с кодом
Мы выполняем декомпозицию - разделяем приложение на более простые части
Давайте вместо слова "части" использовать более подходящее для программирования слово модули
Модули должны взаимодействовать друг с другом
При этом они не должны ничего знать о внутреннем содержании друг друга
Код модуля, его внутренние переменные и функции должны быть недоступны извне
Значит, каждому модулю нужен интерфейс
Под интерфейсом модуля подразумевается система доступа к его функционалу
Мы должны обеспечить закрытость модуля для внешнего вмешательства в его "внутренний мир", при этом обеспечить его открытость для гибкого взаимодействия с другими модулями
Каждый модуль можно, в свою очередь, разложить на еще более простые модули
И так далее...
В процессе неизбежно выяснится, что часть модулей может быть успешно переиспользована в различных частях приложения
Это означает, что переиспользуемые модули не могут находиться в составе модулей, которые их используют
Иначе пришлось бы обращаться из одного модуля к внутреннему содержимому другого, что недопустимо
Мы выделяем такие модули с универсальной функциональностью, чтобы они были доступны из любой части приложения ( с любой глубины )
Теперь встает вопрос о функциональности модулей
Если он будет выполнять множество различных функций, то при обращении к нему нам придется тянуть весь этот функционал вместо того, чтобы взять конкретный инструмент...
Например, что было бы, если бы вместо вызова статического метода Object.assign
нам пришлось бы тащить всю кучу статических методов Object, а потом использовать только один из них?
Object.assign
является отдельным модулем, который выполняет конкретную функцию, и не зависит от других статических методов конструктора Object
Итак, проектирование архитектуры приложения - важнейший этам разработки
Этот принцип обеспечивает четкое разграничение функций ( обязанностей ) модулей
Один модуль отвечает только за что-то одно
Что нам дает соблюдение этого принципа?
Первое - код каждого модуля достаточно прост
Второе - мы легко расширяем функциональность путем добавления новых модулей, но не переписываем уже существующие
Этот принцип иллюстрируют те же статические методы Object
, которые добавляются в каждой новой спецификации языка
Рассмотрим пример объекта user
, который изначально имел два метода - read
и write
Затем мы решаем добавить объекту user
еще один метод - изменить свою аватарку
Нам не приходится изменять код методов read
и write
Мы просто добавляем новый метод с конкретной функциональностью
Позже мы сможем еще больше расширить возможности объекта user
, добавляя новые и новые методы
Пример выше отлично иллюстрирует также и этот приницип
Объект user
открыт для расширения, но при этом исходный код его модулей закрыт для изменения
Мы можем написать интерфейс, позволяющий подключать экземпляру user
необходимые методы
class User {
constructor ( name ) {
this.name = name
}
}
User.updateMethods = function ( methodName, func ) {
this.prototype [ methodName ] = func
}
User.updateMethods ( "write", function ( message ) {
console.log ( `${this.name}: ${message}` )
})
let user = new User ( "Иван" )
user.write ( "Hello!" )
В консоли мы увидим
Иван: Hello!
В этом примере у класса User
предусмотрен интерфейс updateMethods
, расширяющий функциональность экземпляров класса
Давайте используем этот интерфейс для создания нового метода:
User.updateMethods ( "voyage", function ( city ) {
console.log ( `${this.name}: I visit ${city}` )
})
Теперь вызовем новый метод
user.voyage ( "London" )
В консоли мы увидим
Иван: I visit London
Принцип подстановки Барбары Лисков заключается в следующем:
работоспособность кода не должна быть нарушена в результате замены экземпляра класса на экземпляр наследующего класса
это вполне логично: если производный класс только расширяет функциональность базового класса, не изменяя его, то при такой замене все функции базового класса продолжают работать так же, как и ранее
Нам не нужно переписывать модули, работавшие с экземпляром базового класса
В общем-то, это всего лишь соблюдение принципов наследования...
☕
Если мы расширим ранее объявленный класс User
следующим образом:
class RegisteredUser extends User {
constructor ( name, token ) {
super ( name )
let auth = token
this.setIdentity ( token )
this.testIdentity = () => this.getIdentity () === auth
}
getIdentity () {
let token = document.cookie.split("; ")
.filter ( item => item.indexOf ( "auth" ) === 0 )[0]
return token ? token.split( "=" )[1] : null
}
setIdentity ( auth ) {
document.cookie = `auth=${auth}`
}
}
и заменим ранее созданный экземпляр user
на экземпляр нового класса:
user = new RegisteredUser ( "Иван", "xJgb-809/**1Bh" )
то это не приведет к нарушению нормальной работы модулей, оперировавших старым экземпляром user
Как мы уже поняли 😉, модули взаимодействуют друг с другом через интерфейсы
Под интерфейсом мы понимаем публичные методы экземпляра
Дробление интефесов - нормальная практика: если вы написали метод, который делает слишком много разного, вы уже нарушили первый принцип SOLID
Если каждый интефейсный метод экземпляра выполняет одну узкоспециализированную функцию, то нам не нужно его модифицировать при необходимости расширить функциональность модуля
Достаточно добавить еще один узкоспециализированный интерфейсный метод
Например, вернемся к нашему классу User
с его методом write
Предположим, что этот метод может:
- выводить сообщение в консоль
- выводить сообщение на страницу
- выводить сообщение во всплывающем окне
это плохой интерфейс
Нужно три отдельных метода, каждый из которых реализует одну из вышеперечисленных функций
Очевидно, что принцип специализации интерфейсов непосредственно вытекает из первого принципа SOLID
☕
const card = (
function ( pin ) {
let cash = 0
const addCash = sum => cash += sum
const changePin = newPin => pin = newPin
const testPin = pincode => pin === pincode
const showCash = () => console.log ( `Cash: ${cash}` )
const getMoney = sum => {
cash -= sum
console.log ( `Get money: ${sum}` )
}
return function ( operation, sum ) {
operation === 0 ? addCash ( sum ) :
testPin ( prompt ( "Enter pincode" ) ) ?
operation === 1 ? showCash() :
operation === 2 ?
sum <= cash ? getMoney ( sum ) : console.warn ( "Insufficient cash" )
: changePin ( prompt ( "Set your pincode" ) )
: console.error ( "Invalide pincode" )
}
}
)( prompt ( "Set your pincode" ) )
Мы получили модуль с навороченным интерфейсом
Раздробим интерфейс модуля:
const card = (
function ( pin ) {
let cash = 0
const changePin = () => pin = prompt ( "Set your pincode" )
const testPin = pincode => pin === prompt ( "Enter pincode" )
const pinError = () => console.error ( "Invalide pincode" )
const showMoney = () => console.log ( `Cash: ${cash}` )
const getMoney = sum => {
cash -= sum
console.log ( `Get your money: ${sum}` )
}
return {
uppendCash: sum => cash += sum,
showCash: () => testPin() ? showMoney() : pinError (),
getCash: sum => testPin() ? sum <= cash ? getMoney ( sum )
: console.warn ( "Insufficient cash" )
: pinError (),
changePincode: () => testPin() ? changePin () : pinError ()
}
}
)( prompt ( "Set your pincode" ) )
Зависимости...
Что от чего зависит в вашем приложении?
С самого начала вы должны это хорошо продумать
Этот принцип легче всего нарушить в JS, поскольку у нас есть свойство __proto__
экземпляра, которое делает доступным прототип базового класса из экземпляра класса
Пример грубого нарушения этого принципа:
User.updateMethods ( "sedition", function ( prop, val ) {
this.__proto__[prop] = val
})
user.sedition ( "badExample", "You should not do this" )
В этом примере мы предоставили доступ экземплярам к прототипу
Каждый класс является абстрацией
Каждый производный класс является детализацией абстрации верхнего уровня
В примере, приведенном ранее, класс RegisteredUser
является детализией класса User
Другими словами, класс User
является абстрацией более высокого уровня, чем класс RegisteredUser
Поэтому нормально, что класс RegisteredUser
зависит от класса User
, но никак не наоборот
Курсы были созданы для студентов A-Level Ukraine.
Использование данных материалов или любой их части коммерческими школами ( курсами ) является нарушением авторских прав.
1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 |
⏬ |
---|
- Блок-схема алгоритма
- Developer Tools
- Chrome DevTools
- Переменные
- Оператор typeof
- Структуры данных
- Операторы присваивания
- Логические выражения
- Условные операторы
- Инкремент
- Свойство length
- Оператор цикла for
- UTF-8
Homework
- Нативные и host-объекты
- Литерал объекта
- Унаследованные свойства
- Конструктор
- Модель наследования
- Публичные и приватные свойства
- Оператор in
1
Homework
- Итерирующие методы массивов
- Тестирование производительности
- SHA
Homework
- strict mode
- Вычисляемые имена свойств
- Краткий синтаксис методов
- Краткий литерал объекта
- Классы
Homework
- npm
- webpack
Упражнение 1
- ES6 модули
Упражнение 2
- --mode | --watch
Упражнение 3
Упражнение 4
Упражнение 5
Упражнение 6
Упражнение 7
Упражнение 8
Homework
⏫ |
---|