-Кто мы? - К кому ты обращаешься? - Я один здесь на..! Всегда вспоминаю Зелёного Слоника после просмотра практически любого видео)) За сам материал спасибо, довольно доходчиво!
Спасибо за видео! Не знаю, связано это или нет, но после этого видео разработчики moment.js заявили о прекращении разработки новых фич и советуют использовать другие похожие пакеты)
Привет. Как всегда, отличная подача материала. Но! Но вот по ISP ... все не так. Ключевая проблема в видео -- это подмена "зависит" на "реализует". Зависит -- это значит использует (вызывает, читает и т.п.). Реализует -- это значит предоставляет то, что может быть использовано. Если посмотреть на определение ISP, то там ничего про реализацию нет, там -- про зависимость. И объяснить это лучше не на примере User, Landlord и Admin (Где разделение -- это классический пример SRP.), а скорее на примере Readable, Writable. Допустим, у нас есть необходимость писать и читать в стор. Объявим два интерфейса: export interface ReadableStore { read(): Promise; } export interface WritableStore { write(value: T): Promise; } при этом у нас есть IndexDB, где мы хотим хранить наши данные. для реализации мы делаем один класс, который имплементирует оба интерфейса: export class IndexDBStore implements ReadableStore, WritableStore { ... } Далее, допустим, что у нас есть задача, в которой нам надо прочитать некую строку из стора и отправить ее по апи на сервер: const readAndSend = async (store, api) => api.sendString(await store.read()); Так вот ISP о том, какой тип должен быть у store. У нас есть три варианта: - IndexedDBStore - ReadableStore & WritableStore - ReadableStore Согласно ISP первые два варианта его нарушают, а вот третий "Readable" -- самое оно. При этом мы все так же можем вызвать readAndSend, передавая экземпляр IndexDBStore. Для чего это надо? Самое реальное и наглядное применение -- это unit-тесты. const store = { read: jest.fn(); } const api = { sendString: jest.fn(); } expect.assertions(3); store.read.mockResolvedValue('a stirng'); api.sendString.mockResolvedValue(true); expect(await readAndSend(store)).toBe(true); expect(api.sendString).toHaveBeenCalledTimes(1); expect(api.sendString).toHaveBeenCalledWith('a string'); Мы получили корректный с точки зрения TS код теста, и при этом не делали лишнего. В случае с Readable & Writable пришлось бы store в тесте объявить как: const store = { read: jest.fn(), write: jest.fn() }; А в случае с IndexDBStore пришлось бы еще кучу всего добавлять (включая TS-приватные поля класса IndexDBStore). Вторая, более важная, но менее очевидная причина -- это изменения логики. В примере выше, мы видим только один метод api sendString, но вполне вероятно еще есть и sendNumber, sendDate и т.д: export interface Api { sendString(value: string): Promise; sendNumber(value: number): Promise; sendDate(value: Date): Promise; } И вот в один прекрасный момент выясняется, что теперь нам надо не просто отправлять все на сервер, а еще и выводить в консоль. Конечно, мы можем пойти в имплементацию api и там это поменять. Но если апи-клиент у нас был уже вынесен в отдельный пакет (библиотеку), и другие приложения, которые используют тот же api-клиент теперь тоже начнут спамить в консоль, чего бы никому не хотелось. Выход есть -- это декоратор или наследование. Но в обоих этих случаях для того, чтобы вызвать readAndSend, который принимает api: Api, нам надо реализовать все методы в нашей программе. А вот, если readAndSend в качестве api: Pick, то нам достаточно реализовать только один метод. Следующая причина -- это рефакторинг. Допустим однажды выясняется, что мы прекращаем поддержку функционала, который требовал отправку дат на сервер. Т.е. метод sendDate можно убрать. И казалось бы, все просто -- идем в реализацию и правим: и интерфейс, и реализацию. Но, здесь оказывается, что у нас есть код, который содержит дубликат интерфейса Api со всеми полями (когда-то кто-то вынес кусок бизнес-логики в пакет, и чтобы избавиться от зависимости от пакета api-клиентом определил интерфейс Api в этом новом пакете, просто скопировав его из api-клиента). В итоге, код с дубликатом Api теперь не работает, т.к. ему передают объект без метода sendDate (но который этому коду все равно не нужен). Здесь важно, что перенести определение интерфейса в новый пакет -- это было правильное решение (интерфейс принадлежит клиенту -- см. DIP), а вот сделать код, который будет требовать объект, реализующий весь интерфейс -- это как раз и было нарушением ISP. Понятное дело, что можно "кастануть к any", обманув Typescript, и все равно пробросить новый объект в старый код, но здесь мы стреляем себе в ногу, т.к. сразу отключаем кучу других проверок, которые делает TS. Sandbox с примером: codesandbox.io/s/naughty-feistel-kbptzf?file=/src/index.ts И да, все вышесказанное касается TypeScript. С JS такой проблемы не возникнет, т.к. остутствует статическая проверка типов, а зависимость проявляется только в рантайме и только от того, что так или иначе действительно используется. Для JS может быть более актуальным похожий (но отнюдь не тождественный) Закон Деметры (en.wikipedia.org/wiki/Law_of_Demeter). В нашем примере с Api и Store, было бы правильно сделать вот так: const readAndSend = async (read, sendString) => sendString(await read());
Прям на 100% в точку) когда обсуждали формат канала, MyGap взяли за основу и на базе этого уже рисовали что-то свое) Но сразу мы нарисовали просто мужика с бородой, а потом обсуждали, что скучно будет и придумали абстрактного персонажа)
визуализацию делает отдельный человек, поэтому это не сильно влияет на частоту выхода роликов. Чаще проблема в том, что такой контент на постоянной основе писать достаточно сложно. Поэтому иногда пауза бывает между выпусками подольше :)
03:32 на скрине слева ошибка. Нужно было указать что тип Array а не так как указано на видео. На видео показывается что массив юзеров имеет тип либо админ либо квартирант etc
Поддержать нас финансово можно по следующим ссылка: UA-cam: ua-cam.com/channels/lgj-KWiNaOo9H1rz1ISO6Q.htmljoin boosty: boosty.to/sin9k Patreon: www.patreon.com/ITSin9k
-Кто мы?
- К кому ты обращаешься?
- Я один здесь на..!
Всегда вспоминаю Зелёного Слоника после просмотра практически любого видео))
За сам материал спасибо, довольно доходчиво!
Интересно и понятно, спасибо! Когда новые выпуски?
Спасибо :)
вот сейчас напишу анонс текстовый, что во вторник будет новый выпуск)
Очень интересно,спасибо !
Спасибо😊
Очень хорошая подача 👍
спасибо!)
все во имя сообщества)
Благодарю за работу)
Спасибо :) рады стараться!
Спасибо, как раз начинаю работать с TS :)
Мы как раз следующее видео готовили связанное плотно с TS)
@@it-sin9k с нетерпением жду)
Спасибо за видео! Не знаю, связано это или нет, но после этого видео разработчики moment.js заявили о прекращении разработки новых фич и советуют использовать другие похожие пакеты)
ахах) не думаю, что это связано) но moment.js действительно лучше не использовать)
👏👏👏👏👏👏
Лайк + коммент )
спасибо посоны
Привет.
Как всегда, отличная подача материала.
Но! Но вот по ISP ... все не так. Ключевая проблема в видео -- это подмена "зависит" на "реализует". Зависит -- это значит использует (вызывает, читает и т.п.). Реализует -- это значит предоставляет то, что может быть использовано.
Если посмотреть на определение ISP, то там ничего про реализацию нет, там -- про зависимость.
И объяснить это лучше не на примере User, Landlord и Admin (Где разделение -- это классический пример SRP.), а скорее на примере Readable, Writable.
Допустим, у нас есть необходимость писать и читать в стор. Объявим два интерфейса:
export interface ReadableStore {
read(): Promise;
}
export interface WritableStore {
write(value: T): Promise;
}
при этом у нас есть IndexDB, где мы хотим хранить наши данные.
для реализации мы делаем один класс, который имплементирует оба интерфейса:
export class IndexDBStore implements ReadableStore, WritableStore {
...
}
Далее, допустим, что у нас есть задача, в которой нам надо прочитать некую строку из стора и отправить ее по апи на сервер:
const readAndSend = async (store, api) => api.sendString(await store.read());
Так вот ISP о том, какой тип должен быть у store. У нас есть три варианта:
- IndexedDBStore
- ReadableStore & WritableStore
- ReadableStore
Согласно ISP первые два варианта его нарушают, а вот третий "Readable" -- самое оно.
При этом мы все так же можем вызвать readAndSend, передавая экземпляр IndexDBStore.
Для чего это надо?
Самое реальное и наглядное применение -- это unit-тесты.
const store = {
read: jest.fn();
}
const api = {
sendString: jest.fn();
}
expect.assertions(3);
store.read.mockResolvedValue('a stirng');
api.sendString.mockResolvedValue(true);
expect(await readAndSend(store)).toBe(true);
expect(api.sendString).toHaveBeenCalledTimes(1);
expect(api.sendString).toHaveBeenCalledWith('a string');
Мы получили корректный с точки зрения TS код теста, и при этом не делали лишнего. В случае с Readable & Writable пришлось бы store в тесте объявить как:
const store = {
read: jest.fn(),
write: jest.fn()
};
А в случае с IndexDBStore пришлось бы еще кучу всего добавлять (включая TS-приватные поля класса IndexDBStore).
Вторая, более важная, но менее очевидная причина -- это изменения логики.
В примере выше, мы видим только один метод api sendString, но вполне вероятно еще есть и sendNumber, sendDate и т.д:
export interface Api {
sendString(value: string): Promise;
sendNumber(value: number): Promise;
sendDate(value: Date): Promise;
}
И вот в один прекрасный момент выясняется, что теперь нам надо не просто отправлять все на сервер, а еще и выводить в консоль.
Конечно, мы можем пойти в имплементацию api и там это поменять. Но если апи-клиент у нас был уже вынесен в отдельный пакет (библиотеку), и другие приложения, которые используют тот же api-клиент теперь тоже начнут спамить в консоль, чего бы никому не хотелось.
Выход есть -- это декоратор или наследование. Но в обоих этих случаях для того, чтобы вызвать readAndSend, который принимает api: Api, нам надо реализовать все методы в нашей программе. А вот, если readAndSend в качестве api: Pick, то нам достаточно реализовать только один метод.
Следующая причина -- это рефакторинг.
Допустим однажды выясняется, что мы прекращаем поддержку функционала, который требовал отправку дат на сервер. Т.е. метод sendDate можно убрать. И казалось бы, все просто -- идем в реализацию и правим: и интерфейс, и реализацию.
Но, здесь оказывается, что у нас есть код, который содержит дубликат интерфейса Api со всеми полями (когда-то кто-то вынес кусок бизнес-логики в пакет, и чтобы избавиться от зависимости от пакета api-клиентом определил интерфейс Api в этом новом пакете, просто скопировав его из api-клиента). В итоге, код с дубликатом Api теперь не работает, т.к. ему передают объект без метода sendDate (но который этому коду все равно не нужен).
Здесь важно, что перенести определение интерфейса в новый пакет -- это было правильное решение (интерфейс принадлежит клиенту -- см. DIP), а вот сделать код, который будет требовать объект, реализующий весь интерфейс -- это как раз и было нарушением ISP.
Понятное дело, что можно "кастануть к any", обманув Typescript, и все равно пробросить новый объект в старый код, но здесь мы стреляем себе в ногу, т.к. сразу отключаем кучу других проверок, которые делает TS.
Sandbox с примером: codesandbox.io/s/naughty-feistel-kbptzf?file=/src/index.ts
И да, все вышесказанное касается TypeScript. С JS такой проблемы не возникнет, т.к. остутствует статическая проверка типов, а зависимость проявляется только в рантайме и только от того, что так или иначе действительно используется. Для JS может быть более актуальным похожий (но отнюдь не тождественный) Закон Деметры (en.wikipedia.org/wiki/Law_of_Demeter).
В нашем примере с Api и Store, было бы правильно сделать вот так:
const readAndSend = async (read, sendString) => sendString(await read());
Спасибо, очень полезно 😀
Нас очень радуют такие комментарии)
лол, голос на скорости х1.25 звучит более естественно, чем оригинал
запись аудио дорожки это особый стресс для меня)
очень тяжело мне дается)
🙃
Спасибо за видос! По стилю подачи материала вдохновлялся каналом MyGap? )
Прям на 100% в точку) когда обсуждали формат канала, MyGap взяли за основу и на базе этого уже рисовали что-то свое) Но сразу мы нарисовали просто мужика с бородой, а потом обсуждали, что скучно будет и придумали абстрактного персонажа)
Мы как раз обсуждали, что стиля нехватает нашим роликам) думаем на донаты привлечь дизайнера, чтобы помог стилизовать ролики)
визуализацию делает отдельный человек, поэтому это не сильно влияет на частоту выхода роликов. Чаще проблема в том, что такой контент на постоянной основе писать достаточно сложно. Поэтому иногда пауза бывает между выпусками подольше :)
03:32 на скрине слева ошибка. Нужно было указать что тип Array а не так как указано на видео. На видео показывается что массив юзеров имеет тип либо админ либо квартирант etc
ты абсолютно прав) я набросал это не тестируя, а потом увидел косяк, только после публикации видео) лайк за внимательность)
@@it-sin9k а то что объединение интерфейсов идёт, а не типов никого не смутило?))
В Go этот принцип доведён до совершенства!
О, ты из Минска?)
Да :)
@@it-sin9k кайф, я тоже! не эмигрировал еще?)
тут так пустовато чутка стало без айтишечки
В данный момент тоже уехал :)
Надо коммент написать
Это правильно!) комментарий надо всегда писать)
У вас есть патреон?
Поддержать нас финансово можно по следующим ссылка:
UA-cam: ua-cam.com/channels/lgj-KWiNaOo9H1rz1ISO6Q.htmljoin
boosty: boosty.to/sin9k
Patreon: www.patreon.com/ITSin9k
CRP instead of CPR
Формат ~8-ми хвилин досить комфортний.
Так и задумывалось!) длинные видео тяжело решиться смотреть)