Ребятушки, многие из вас писали по поводу непоняток в коде, чаще всего выбора CalculatorType и тд. Демо код из видео я немного переделал и превратил в небольшую игрульку, выложил её на гитхаб С ней ознакомиться можно здесь github.com/Haywaar/PatternDemoStorage/tree/main/Assets/Patterns/DIExample
Спасибо. 1. DI может решить проблему динамического изменения зависимости в процессе (изменить калькулятор урона в процессе игры)? 2. Метод "Construct" у Player - причины названия? Это личное или кто-то из умных дядек так советует? (из СИ я привык к Init) ПС: раздел "Dipendency Injection, главное" 5:52 - 6:39 (7:29) сильно сбивает с толку, понимание смысла DI распыляет, размазывает, я этот раздел просто пропускаю, ничего не теряя в понимании... Раздел "Inversion of Control, Инверсия потока управления" - вот главное, и по мне, должно идти вначале, как раз до 5:52 или вместо, потому что именно оно является основой DI и объясняет принцип DI :*
Если бы все объяснения начинались с "в чем вообще проблема и что будет без этого решения", мы наверное бы уже Марс терраформировали. Спасибо за хорошее объяснение.
Автор ты лучший) Прошерстил разные видосы на эту и подобные темы, твоя подача самая внятная и простая. "Сложное сделать просто, а простое сделать сложно" Респект
Огромная благодарность! Отдельное спасибо за неторопливую подачу материала. А то складывается ощущение, что люди пытаются впихнуть весь материал своего ролика в 60 секунд, поэтому тараторят что-то там неразборчивое.
Скажу лишь, что DI использую интуитивно. У меня что-то вроде правил. 1. Делай все зависимости на интерфейсах; 2. Пихай все зависимости в Инжектор; 3. Инжекть через конструктор, если не MonoBehaviour, если MonoBehaviour, то через [Inject], либо специальный метод а-ля Init(VContainer); Это моё такое "понимание" DI, другие разработчики вроде на такую мою последовательность не жалуются. Надо будет пересмотреть видео еще раз. Реально, если говорить за теорию, то даже с наличием практики было тяжко сразу всё понять и запомнить:3
Интересный плейлист, но было бы лучше для тех кто не знает эти паттерны в начале видео за минуту обЪяснить что это и зачем используется с примерами чтобы понять нужен ли тебе этот паттерн.
Проблема Singleton-ов стоит в том, что GC не чистит статические данные, так как они будут существовать всю продолжительность жизни программы. Вот поэтому Singleton это антипаттерн. Он тупо засирает память на больших и средних проектах. + Тестирование слабое и нарушение SRP; Насчёт минуса DI: "Тяжелее дебажить". Это так при условии, что у вас 1 DI контейнер. В проекте АА и ААА мы, создаем сразу несколько контейнеров которые очень сильно напоминают работу Assembly Definition(визуально). Это очень похожий принцип, того как в книге "Level Up Your Code" от Unity изобразили паттерн Observer, где для его улучшения создают EventManager. Спасибо за видео, подтянул теорию.
Интересное видео. Спасибо. Но я порекомендую добавить побольше устного и визуально дотошного объяснения. К примеру: Где плюсы "Удобен на проектах среднего и большого размера" - потому что: *И тут объясняешь" (Даже если ты говорил это раньше, повторение является хорошим закрепление материала)
Интересная рекомендация :) Я наоборот себя постоянно фильтрую и стараюсь не растекаться мыслью по древу, а тут получается есть желающие еще больше подробностей и объяснений) Подумаю :)
Пример отлично подобран! В начале я не понял, почему бы не сделать в пример какой-нибудь Input(базовый класс), а в каком-нибудь контроллере уже заменять на KeyboardInput, MobileInput и т.д.
Да, на практике чаще всего клавиатура самый очевидный сервис, но я решил что пример с сервисом расчёта урона более наглядный будет, ибо можно всё пощупать и посмотреть сразу же
@@sergeykazantsev1655 а подскажите пожалуйста такой момент. вы говорили что с ДИ код будет меньше без свитчей и тд. А потом когда показали код на 9.15 там и свич кейсы есть, и куча классов дамаджКалькуляторРендж... как показалось кода, стало больше, запутанней... мы прекрасно обходились классами. чет вообще не понял что же изменилось...
@@КатавыйОбзорщик потому что на 9.15 тайминг показан Injector, который а) может быть похожим но чуть другим в плане реализации, а именно быть фабрикой (Factory) и реально в рунтайме по типу (type) или другим задачам - инстанциировать конкретный экземпляр DamageCalculator, и всеравно такой switch (type) будет, главное что он в Factory а не внутри Player (который не должен знать в данной задаче, впрочем может быть и другая задача и тогда Player будет не в конструкторе получать а скажем внутри иметь Weapon и от Weapon уже будет зависеть какой Calculator у этого оружия); или (б) это может быть статичный выбор зависимости (без switch, без решения подтипа) когда "геймдиз прототипирует" и тогда везде в конструкторы пойдет одинаковый инстанс (или одинаковый подтип инстанса) скажем SimpleDamageCalculator; p.s. а кода будет полюбому больше за счет Factory (и выбора) или даже за счет Interface-ов, это минус паттернов такого типа
С DI свичи всё ещё остаются, но эти свичи остаются только в одном месте: на самом верху где вы определяете класс который будет решать ту или иную задачу. Магия DI заключается в том, что классам которые получают зависимости абсолютно всё равно с какой конкретно реализацией они работают.
Спасибо за видео) Вопрос: Если вместо интерфейса, сделать абстрактный класс с абстрактным методом и все виды расчета урона прописывать в классах наследующих его и в injector-е прокидывать зависимость с помощью UpCast-а , это все еще будет паттерн Dependency Injection?
Да, мне кажется тут не важно, внедряете ли вы его как интерфейс или как абстрактный класс, по структуре, раз вы можете вставить туда разные реализации, UpCasе-я их как базовый класс - всё ок. В DI нет строгого правила - ВНЕДРЯЙТЕ ТОЛЬКО ИНТЕРФЕЙС. Абстрактный класс я думаю что тоже пойдет
Мне пока не хватает компетенции чтобы про ECS рассказать так как ручками я его никогда не трогал и знаю только теорию. Но когда-нибудь я до него доберусь :D
а вот если у нас есть данный словарь: private static Dictionary Services = new(); и нам надо регистрировать и как то получать разные реализации интерфейса,если мы получаем объект по Type?
@@sergeykazantsev1655 по сути DI это и есть сервис локатор,который сам прокидывает зависимости,в моем проекте он это делает через рефлексию и аттрибуты метода
Обычно между DI и сервис локатором есть разница в моменте когда вы внедряете сервисы. В DI внедрение происходит сразу, при создании или инициализации класса. Получить же сервисы через сервис локатор вы можете в любой момент выполнения, а не только инициализации. Потому обычно DI и принято отличать от сервис локатора
9:16 тут разве нет нарушения открытости-закрытости? И ещё вопрос, а если у нас текущий урон задаётся на уровне конфигурации, то при смене типа урона(а он будет меняться достаточно часто, насколько понимаю) не придется ли переконфигурировать приложение заново?
Я не до конца понимаю о нарушении OCP в каком именно классе вы имеете ввиду. Если о DamageCalculatorRange - то в нём я не вижу нарушения OCP. Если вы говорите об Injector-е, то да, если у нас постоянно будут добавляться новые калькуляторы, switch-case будет расти. Но все равно где-то должен быть класс который будет связывать конфиги и конкретные классы, скажет - "Если нам нужен урон с перепадом, надо использовать DamageCalculatorRange"
Что касается смены типа урона: как раз таки приложение конфигурировать не придётся, придётся писать только новые калькуляторы. Возможно, надо будет переделать интерфейс изначальный если будет много модификаторов, надо проанализировать ситуацию, но в целом мы стремимся именно к тому, чтобы при новом калькуляторе мы писали один новый класс и он хорошо работал с нашей написанной системой
Да, в видео это я не объяснил, потому написал в открытом гите демку github.com/Haywaar/PatternDemoStorage/tree/main/Assets/Patterns/DIExample Injector является точкой входа - то есть стартовым скриптом который запускается в самом начале игры. Параметры CalculatorType берутся на основе стартовых конфигов - сейчас мы в самом Injector-е в инспекторе выбираем один из вариантов. Подразумевается что выбор того или иного калькулятора основан на конфигах которые прилетают нам откуда-то снаружи(условно гд потрогал настройки на сервере и мы их подтянули)
CallculatorType в плеер не целесообразно передавать именно потому, чтобы Player не распухал. DI позволяет эту проблему распухания обойти. Решение с CalculatorType для метода Attack является плохим, на 04:25 можно посмотреть. На 08:28 есть сравнение старого и нового правильного плеера. Таким образом CalculatorType нам не нужен в методе Attack если мы с калькулятором работаем на уровне интерфейса)
DI это скорее архитектурный паттерн, который говорит о том, как работать с зависимостями в коде(внедрять на уровне интерфейса) Стратегия это более прикладной и поведенческий паттерн, который ту же механику использует для удобного переключения сервисов во время выполнения программы. Они действительно очень похожи, но DI говорит об организации зависимостей в вашем проекте, а паттерн стратегия это конкретное использование этого же механизма для переключения одного поведения на другое
@sergeykazantsev1655 в рассказе про DI вы никак не упомянули DI-контейнер. Поэтому вопрос, можно ли считать, что класс-injector в вашем примере - это и есть DI-контейнер?
Я бы сказал так. DI-контейнер это и есть Injector. У обоих задача - прокидывать зависимости в классы через интерфейс. Но обычно под DI-контейнером подразумевается более сложная сущность, некий Injector на максималках или же прокачанный Injector. Задача Injector-а - внедрить зависимости в классы через интерфейс. Задача DI-контейнера - выбрать/сгенерировать зависимости и внедрить их в классы через интерфейс. В DI контейнере может быть много внутреннего функционала - фабрики для создания сервисов, контроль за тем, нужен ли кому-то сервис или можно его отрубить чтобы в памяти не висел, подкапотная логика прокидывания зависимостей как в Zenject-е, чтобы ты не ручками зависимости прокидывал а просто бахнул тэг [Inject] и оно само прокинулось и так далее. Так что в моём примере можно сказать что Injector это и есть DI-контейнер, только этот контейнер жутко урезанный и ограниченный
В масштабе и области применения, как мне кажется. DI это паттерн который объясняет как строить архитектуру. Стратегия это паттерн основанный на di, предназначенный для реализации разных способов выполнения одной и той же задачи
В Init тип калькулятора приходит сверху и уже не так важно как именно. Тип калькулятора может прилетать по разному: с сервака/JSON-а/ScriptableObject-а. Наша главная задача обработать команду "Я хочу калькулятор А, выбери нужную логику обработки и расчёта". Ту самую нужную логику обработки и расчёта реализует Injector. Кто именно отправляет команду - там не важно Блин, походу надо написать демку, а то много таких вопросов поступает)
09:00. Player не знает с каким типом атаки он работает, ему это не нужно. О типе атаки знает Injector. Он помещает в Player какой-то тип калькулятора, а Player работает с калькулятором на уровне интерфейса и может не знать с какой конкретной реализацией он работает. В этом и есть идея DIP.
У меня на канале есть рубрика "Паттерны на практике" где я пишу простые игры и выкладываю их на гитхаб и на примере игр показываю решения. Сейчас есть одна игра, там нет DI, он будет во второй игре, когда я до неё доползу :) Если кирпич на голову не упадёт через полтора-два месяца выложу игру)
@@sergeykazantsev1655 спасибо . . . но она вроде как на Unity, а гораздо больше юзеров работают с WinForms . .. поэтому желательно на них чтобы была игра.
@@sergeykazantsev1655 если вы юнитолог тогда подскажите в какой среде разработки пишется обработка графики . . . с кодом понятно - визюал студия . . . а графика в какой среде пишется ? . . . и подскажите, пожалста, какое видео смотреть чтобы с нулю начать обучаться юнити
Юнити это и игровой движок и среда, в ней как в редакторе вы размещаете сцены и объекты и в ней же вы подключаете написанный код как некоторые компоненты. Насчёт курсов по юнити с нуля, затрудняюсь ответить, сам в свое время купил курс на юдемии 6 лет назад, но за это время многое могло измениться
di как паттерн ничего общего не имеет с принципом на буковку D, никто не запрещает в di использовать конкретные реализации и поставлять их клиентам, то что di может внедрять конкретную реализацию через интерфейс - это следствие. Был как раз упомянут service locator, там тоже всё прекрасно, есть инстансы, есть интерфейсы и при этом есть возможность получать зависимости в виде интерфейса, но проблема была в том, что в классической реализации локатора клиенту поставлялся этот мешок с зависимостями и не понятно было что в нём содержится, а если потребовать такой интерфейс выдаст ли он мне нужный и тому подобные вещи, а ведь буковка D соблюдается при таком подходе - никаких конкретных реализаций, только АБСТРАКЦИИ. Основное что нужно понимать, что в вашей программе при таком подходе появляется ровно одна точка, где разрешаются ЗАВИСИМОСТИ(не важно от чего, хоть от инта) и уменьшаете то количество мест, где вручную нужно было создавать и разрешать зависимости, с таким подходом вы пишете код и перестаёте думать о том как вам придёт эта зависимость или как и где её опрокинуть(хотя наивно так тоже думать), и тем самым вы больше добиваетесь прозрачности в потоке выполнения программы, чем пытаетесь соблюдать какой-то принцип на букву D. Поэтому с лёгкостью можно создавать фреймворки для таких штук, но также стоит упомянуть, что в таком случае вы отдаёте контроль над выполнением вашей программы другому инструменту, что также может сказаться в дальнейшем, но это уже больше детал
1. "DI как паттерн не имеет ничего с DIP" - к сожалению я с этим не могу согласиться. Очень важной частью DI как паттерна является внедрение зависимостей на уровне ИНТЕРФЕЙСА. А DIP говорит об организации кода на уровне АБСТРАКЦИЙ. Я уже не говорю о том что практически в любом материале по DI тебе в связку накидывают материалы про IoC который напрямую связан с DIP. 2. Можно ли использовать DI без интерфейса и прокидывать зависимости напрямую как конкретные реализации? Да, конечно. Но я бы разделял DI как паттерн(внедрение зависимостей на уровне интерфейса) и DI как простое прокидывание одних классов в другие. Если из DI убрать вот эту часть про "на уровне интерфейса" - что от него останется? Идея собирать зависимости в одном месте? А чем это отличается от какого-нибудь Entry Point да или тот же SRP можно сюда притянуть. Лично я не считаю простое прокидывание одних классов в другие паттерном, так же как не считаю что Фабрика - это паттерн. Абстрактная фабрика или фабричный метод - это паттерны проектирования. Фабрика - это класс который порождает объекты но в этом нет никакого шаблонного решения. Возможно вкусовщина
@@sergeykazantsev1655 Понимаете, то что вы называете присуще и сервис локатору, основная идея DI, что у вас появляется точка конфигурации вашего приложения, вы не размазываете конфигурацию по приложению, а собираете всё в одном месте. Объект перестаёт думать о зависимостях и как ему их получить, он либо их разрешает сам, либо делегирует это слою инициализации приложения(что в народе и зовётся entry point). Насчёт последнего абзаца, это и есть основная идея DI, зависимости в классе настраиваются в каком-то инжекторе или контейнере или каким-то фреймворком. Класс перестаёт думать как ему получить ту или иную зависимость, он просто перестаёт отвечать за это. А программировать на уровне интерфейсов это уже совсем другое, если класс зависит от абстракции, то ему и поставим конкретную реализацию под абстракцией, которую если что можно поменять в той самой точке приложения и не думать, а в каких ещё местах надо было это сделать. В общем, мне просто не нравится что в DI начинают рассказывать следствия этого подхода, DI - это про получение зависимостей, то что вы описываете в ролике это ioc-контейнеры. Тут же важно понимать само действие, классу их поставляют или он сам их запрашивает или вообще сам их создаёт. И про первый пункт, в этом и проблема, что рассказывают про ioc контейнеры, а не про DI как таковой. Как и говорил выше, ioc контейнеры это конкретная реализация принципа DI, но не DI как таковой. Просто часто наблюдаю ситуации, когда говорят про DI и сразу ответ, а это какой-то ioc-контейнер с именем X.
Ну я думаю что мы друг друга услышали но остались при своём мнении. Наверное можно назвать это вкусовщиной потому что разные источники будут говорить разное. С моей точки зрения ключевая идея DI именно в внедрении зависимостей на уровне интерфейса. Сервис локатор выполняет схожие задачи но несколько другим образом, вы это знаете Я не считаю что главная идея DI - "у вас все зависимости должны быть собраны в одном месте". Таким местом(классом) в которым создаются зависимости EntryPoint-ы да и тот же сервис локатор на этапе инициализации.
Вообще DI нарушает приципы solid и создает огромную путаницу в коде. Чем больше зависимостей добавляешь тем хуже, странный подход со стороны продакшенов.
2,3,4 принципы. Так же, использование di и рядом не стоит с KISS/DRY. Так же, с моей точки зрения ООП идет нога в ногу с SOLID. Классы доджны быть инкапсулированы в ООП и иметь единую ответственность по SOLID?, но если мы будем что-то пытаться засунуть в закрытый класс, который должени иметь свою зону отвествунности через DI, мы просто теряем смысл писать на ООП и под SOLID. DI отлично в ECS системах впишется. А большинство потребностей "Внедрения зависимостей" решаются прямыми руками и использованием делегатов. Но может я и не прав.@@sergeykazantsev1655
Если у вас класс А содержит внутри себя класс Б, это не значит что он нарушает принцип единой ответственности. SRP он несколько про другое как мне кажется, он про то что методы, обрабатывающую одну конкретную сущность должны быть в одном месте. Это упрощает модификацию, так как не надо по всему проекту прыгать и про то чтобы организовать классы так, чтобы они не распухали.
Наоборот! DI это вся суть ооп и солида. Просто при прокидывании зависимостей ты должен привязываться не к конкретным реализациям, а к абстрациям. Это тебе дает огромную силу и гибкость. Процесс тестирования упрощается в разы.
Я как то приплетал di как устройство компа. Где есть центальная мать и шина которые следят за состоянием устройств и отвечают за взаимодейсивие и разграничивающая ответственность, которой нужно питание, для взаимодействия устройств, устройства это классы синглтоны копнув внутрь такого увидим почти целый мир рекурсивной реальности, похожую на мать диай шину, мироустройства разные/одановковые, все взаимодействуют как пчёлки гармонинчно, которые должны весьма специфично и в определенном порядке (билдеру привет) взаимодействовать. Можно накинуть планки оперативы, добавить pci ультрамеганавороченный класс вывода звука. Слой классов/устройств которые готовят для тебя финальную картинку (выходящие данные, собранный обьект приложения) на монитор. И слой классов внешних девайсов которые можно плуг/эн/плей подключать, отключать, не нарушая сомтояния всего компа. Ну тупо когда комп ребутает или умирает когда ты вставляешь или вынимаеш например флешку или подключаешь монитор.
Ребятушки, многие из вас писали по поводу непоняток в коде, чаще всего выбора CalculatorType и тд. Демо код из видео я немного переделал и превратил в небольшую игрульку, выложил её на гитхаб
С ней ознакомиться можно здесь
github.com/Haywaar/PatternDemoStorage/tree/main/Assets/Patterns/DIExample
Спасибо.
1. DI может решить проблему динамического изменения зависимости в процессе (изменить калькулятор урона в процессе игры)?
2. Метод "Construct" у Player - причины названия? Это личное или кто-то из умных дядек так советует? (из СИ я привык к Init)
ПС: раздел "Dipendency Injection, главное" 5:52 - 6:39 (7:29) сильно сбивает с толку, понимание смысла DI распыляет, размазывает, я этот раздел просто пропускаю, ничего не теряя в понимании... Раздел "Inversion of Control, Инверсия потока управления" - вот главное, и по мне, должно идти вначале, как раз до 5:52 или вместо, потому что именно оно является основой DI и объясняет принцип DI :*
Если бы все объяснения начинались с "в чем вообще проблема и что будет без этого решения", мы наверное бы уже Марс терраформировали.
Спасибо за хорошее объяснение.
И вам спасибо за добрые слова)
Самое понятное объяснение с крутой анимацией для понимания, которе я видел на youtube. Сегрей, большое спасибо!👍
Автор ты лучший) Прошерстил разные видосы на эту и подобные темы, твоя подача самая внятная и простая. "Сложное сделать просто, а простое сделать сложно" Респект
Отличная подача. Зря в колледж пошёл, нужно было по вашим видео учиться. Как всегда очень круто! Спасибо.
Огромная благодарность! Отдельное спасибо за неторопливую подачу материала. А то складывается ощущение, что люди пытаются впихнуть весь материал своего ролика в 60 секунд, поэтому тараторят что-то там неразборчивое.
Сумасшедшее качество подачи, почему я только сейчас наткнулся?
Подписываюсь, спасибо автор)
Скажу лишь, что DI использую интуитивно.
У меня что-то вроде правил.
1. Делай все зависимости на интерфейсах;
2. Пихай все зависимости в Инжектор;
3. Инжекть через конструктор, если не MonoBehaviour, если MonoBehaviour, то через [Inject], либо специальный метод а-ля Init(VContainer);
Это моё такое "понимание" DI, другие разработчики вроде на такую мою последовательность не жалуются.
Надо будет пересмотреть видео еще раз. Реально, если говорить за теорию, то даже с наличием практики было тяжко сразу всё понять и запомнить:3
Да, я приблизительно так же DI понимаю.
Какой же божественный канал, того глядишь стану гейм девелопером
Спасибо. Отличный канал, отличное изложение материала.
во, то что надо! целый плейлист с паттернами👍👍
очень четко и кратко. хорошо использовать, как шпаргалку
Лучший урок из тех что я видел
Интересный плейлист, но было бы лучше для тех кто не знает эти паттерны в начале видео за минуту обЪяснить что это и зачем используется с примерами чтобы понять нужен ли тебе этот паттерн.
Со всем изложенным полностью согласен 👌
Спасибо тебе, хороший человек.
Твои видео прям находка! Доходчиво без воды и с классной инфографикой! Пасиииба! :**
Большое спасибо за видео! все понятно
Проблема Singleton-ов стоит в том, что GC не чистит статические данные, так как они будут существовать всю продолжительность жизни программы.
Вот поэтому Singleton это антипаттерн. Он тупо засирает память на больших и средних проектах.
+ Тестирование слабое и нарушение SRP;
Насчёт минуса DI: "Тяжелее дебажить".
Это так при условии, что у вас 1 DI контейнер. В проекте АА и ААА мы, создаем сразу несколько контейнеров которые очень сильно напоминают работу Assembly Definition(визуально).
Это очень похожий принцип, того как в книге "Level Up Your Code" от Unity изобразили паттерн Observer, где для его улучшения создают EventManager.
Спасибо за видео, подтянул теорию.
Отличный канал) хорошо объясняешь. Обычно не оставляю комментарии но тут не смог пройти мимо
Очень интересный урок! Подписываюсь на ваш канал
Орнул с Rabotyaga injection)))
Спасибо за видео!
Интересное видео. Спасибо. Но я порекомендую добавить побольше устного и визуально дотошного объяснения. К примеру: Где плюсы "Удобен на проектах среднего и большого размера" - потому что: *И тут объясняешь" (Даже если ты говорил это раньше, повторение является хорошим закрепление материала)
Интересная рекомендация :) Я наоборот себя постоянно фильтрую и стараюсь не растекаться мыслью по древу, а тут получается есть желающие еще больше подробностей и объяснений) Подумаю :)
Здорово, многое понял
Ты лучший, невероятно понятные видео ❤
Лайк, подписка. Четко, интересно, понятно. Спасибо!
Спасибо за ролик, супер! Даёшь ZENJECT!!!
очень круто!
Подписка! Однозначно)
Вау!!! Зашло)) для меня оооочень полезная инфа, т. к. сейчас копаюсь в Spring и Angular. Спасибо огромное)
Всё было крайне понятно! Большое спасибо!
Спасибо 👍🏻
Бро! Ты лучший
Пример отлично подобран! В начале я не понял, почему бы не сделать в пример какой-нибудь Input(базовый класс), а в каком-нибудь контроллере уже заменять на KeyboardInput, MobileInput и т.д.
Да, на практике чаще всего клавиатура самый очевидный сервис, но я решил что пример с сервисом расчёта урона более наглядный будет, ибо можно всё пощупать и посмотреть сразу же
@@sergeykazantsev1655 а подскажите пожалуйста такой момент. вы говорили что с ДИ код будет меньше без свитчей и тд. А потом когда показали код на 9.15 там и свич кейсы есть, и куча классов дамаджКалькуляторРендж... как показалось кода, стало больше, запутанней...
мы прекрасно обходились классами. чет вообще не понял что же изменилось...
@@КатавыйОбзорщик потому что на 9.15 тайминг показан Injector, который а) может быть похожим но чуть другим в плане реализации, а именно быть фабрикой (Factory) и реально в рунтайме по типу (type) или другим задачам - инстанциировать конкретный экземпляр DamageCalculator, и всеравно такой switch (type) будет, главное что он в Factory а не внутри Player (который не должен знать в данной задаче, впрочем может быть и другая задача и тогда Player будет не в конструкторе получать а скажем внутри иметь Weapon и от Weapon уже будет зависеть какой Calculator у этого оружия); или (б) это может быть статичный выбор зависимости (без switch, без решения подтипа) когда "геймдиз прототипирует" и тогда везде в конструкторы пойдет одинаковый инстанс (или одинаковый подтип инстанса) скажем SimpleDamageCalculator; p.s. а кода будет полюбому больше за счет Factory (и выбора) или даже за счет Interface-ов, это минус паттернов такого типа
плохо что нет целостного кода
С DI свичи всё ещё остаются, но эти свичи остаются только в одном месте: на самом верху где вы определяете класс который будет решать ту или иную задачу. Магия DI заключается в том, что классам которые получают зависимости абсолютно всё равно с какой конкретно реализацией они работают.
спасибо
Спасибо за видео) Вопрос: Если вместо интерфейса, сделать абстрактный класс с абстрактным методом и все виды расчета урона прописывать в классах наследующих его и в injector-е прокидывать зависимость с помощью UpCast-а , это все еще будет паттерн Dependency Injection?
Да, мне кажется тут не важно, внедряете ли вы его как интерфейс или как абстрактный класс, по структуре, раз вы можете вставить туда разные реализации, UpCasе-я их как базовый класс - всё ок. В DI нет строгого правила - ВНЕДРЯЙТЕ ТОЛЬКО ИНТЕРФЕЙС. Абстрактный класс я думаю что тоже пойдет
Классный канал! А разбирать зенжект будем? Или быть может екс?
В ближайшее время не планирую, уж слишком он массивный, надо самому тщательно его прокопать, да и вроде есть уже ребята которые неплохо его разобрали
Даа... синглтон, где прокиданы все зависимости... знакомо :D
Кстати, а что на счет ECS?))
Мне пока не хватает компетенции чтобы про ECS рассказать так как ручками я его никогда не трогал и знаю только теорию. Но когда-нибудь я до него доберусь :D
Таких каналов в русском сегменте можно по пальцам одной руки пересчитать буквально
а вот если у нас есть данный словарь:
private static Dictionary Services = new();
и нам надо регистрировать и как то получать разные реализации интерфейса,если мы получаем объект по Type?
Это больше на сервис локатор похоже)
@@sergeykazantsev1655 по сути DI это и есть сервис локатор,который сам прокидывает зависимости,в моем проекте он это делает через рефлексию и аттрибуты метода
и еще хотелось бы узнать как грамотно при такой системе сделать Entry Point, нигде не могу найти полной информации
Обычно между DI и сервис локатором есть разница в моменте когда вы внедряете сервисы. В DI внедрение происходит сразу, при создании или инициализации класса.
Получить же сервисы через сервис локатор вы можете в любой момент выполнения, а не только инициализации. Потому обычно DI и принято отличать от сервис локатора
А в чём конкретно трудности с сочетанием DI и Entry Point у вас возникают?
9:16 тут разве нет нарушения открытости-закрытости? И ещё вопрос, а если у нас текущий урон задаётся на уровне конфигурации, то при смене типа урона(а он будет меняться достаточно часто, насколько понимаю) не придется ли переконфигурировать приложение заново?
Я не до конца понимаю о нарушении OCP в каком именно классе вы имеете ввиду. Если о DamageCalculatorRange - то в нём я не вижу нарушения OCP. Если вы говорите об Injector-е, то да, если у нас постоянно будут добавляться новые калькуляторы, switch-case будет расти. Но все равно где-то должен быть класс который будет связывать конфиги и конкретные классы, скажет - "Если нам нужен урон с перепадом, надо использовать DamageCalculatorRange"
Что касается смены типа урона: как раз таки приложение конфигурировать не придётся, придётся писать только новые калькуляторы. Возможно, надо будет переделать интерфейс изначальный если будет много модификаторов, надо проанализировать ситуацию, но в целом мы стремимся именно к тому, чтобы при новом калькуляторе мы писали один новый класс и он хорошо работал с нашей написанной системой
@@sergeykazantsev1655 Да, именно про свитч кейс говорил. Спасибо за видосы, очень круто сделанные! :)
я что-то не догоняю откуда вызывается в Injector метод Init и берутся параметры CalculatorType
Да, в видео это я не объяснил, потому написал в открытом гите демку
github.com/Haywaar/PatternDemoStorage/tree/main/Assets/Patterns/DIExample
Injector является точкой входа - то есть стартовым скриптом который запускается в самом начале игры.
Параметры CalculatorType берутся на основе стартовых конфигов - сейчас мы в самом Injector-е в инспекторе выбираем один из вариантов.
Подразумевается что выбор того или иного калькулятора основан на конфигах которые прилетают нам откуда-то снаружи(условно гд потрогал настройки на сервере и мы их подтянули)
Вот ты ебать красиво стелишь, приятно слушать!
👍
а это нормально что в обновленном Player в Attack мы не передаем CalculatorType? Для простоты было сделано?)
CallculatorType в плеер не целесообразно передавать именно потому, чтобы Player не распухал. DI позволяет эту проблему распухания обойти. Решение с CalculatorType для метода Attack является плохим, на 04:25 можно посмотреть. На 08:28 есть сравнение старого и нового правильного плеера. Таким образом CalculatorType нам не нужен в методе Attack если мы с калькулятором работаем на уровне интерфейса)
Здравствуйте! Вот вроде понял суть, но вообще не понял его отличие от паттерна стратегии. Можно пояснить? Желательно с примерами.
DI это скорее архитектурный паттерн, который говорит о том, как работать с зависимостями в коде(внедрять на уровне интерфейса)
Стратегия это более прикладной и поведенческий паттерн, который ту же механику использует для удобного переключения сервисов во время выполнения программы.
Они действительно очень похожи, но DI говорит об организации зависимостей в вашем проекте, а паттерн стратегия это конкретное использование этого же механизма для переключения одного поведения на другое
@sergeykazantsev1655 в рассказе про DI вы никак не упомянули DI-контейнер. Поэтому вопрос, можно ли считать, что класс-injector в вашем примере - это и есть DI-контейнер?
Я бы сказал так. DI-контейнер это и есть Injector. У обоих задача - прокидывать зависимости в классы через интерфейс. Но обычно под DI-контейнером подразумевается более сложная сущность, некий Injector на максималках или же прокачанный Injector.
Задача Injector-а - внедрить зависимости в классы через интерфейс.
Задача DI-контейнера - выбрать/сгенерировать зависимости и внедрить их в классы через интерфейс. В DI контейнере может быть много внутреннего функционала - фабрики для создания сервисов, контроль за тем, нужен ли кому-то сервис или можно его отрубить чтобы в памяти не висел, подкапотная логика прокидывания зависимостей как в Zenject-е, чтобы ты не ручками зависимости прокидывал а просто бахнул тэг [Inject] и оно само прокинулось и так далее.
Так что в моём примере можно сказать что Injector это и есть DI-контейнер, только этот контейнер жутко урезанный и ограниченный
Очень напоминает паттерн стратегия, в чем их ключевая разница?
В масштабе и области применения, как мне кажется. DI это паттерн который объясняет как строить архитектуру. Стратегия это паттерн основанный на di, предназначенный для реализации разных способов выполнения одной и той же задачи
А каким образом мы передаём тип калькулятора в Init?
В Init тип калькулятора приходит сверху и уже не так важно как именно. Тип калькулятора может прилетать по разному: с сервака/JSON-а/ScriptableObject-а. Наша главная задача обработать команду "Я хочу калькулятор А, выбери нужную логику обработки и расчёта". Ту самую нужную логику обработки и расчёта реализует Injector. Кто именно отправляет команду - там не важно
Блин, походу надо написать демку, а то много таких вопросов поступает)
А как player понимает тип атаки ? Нигде же не передается тип атаки
09:00. Player не знает с каким типом атаки он работает, ему это не нужно. О типе атаки знает Injector. Он помещает в Player какой-то тип калькулятора, а Player работает с калькулятором на уровне интерфейса и может не знать с какой конкретной реализацией он работает. В этом и есть идея DIP.
Сейчас DI даже с джунов спрашивают 😢
Да, к сожалению, требования подросли
очень было бы хорошо если бы вы показали весь код чтобы юзеры могли его запустить на своих ПК
У меня на канале есть рубрика "Паттерны на практике" где я пишу простые игры и выкладываю их на гитхаб и на примере игр показываю решения. Сейчас есть одна игра, там нет DI, он будет во второй игре, когда я до неё доползу :) Если кирпич на голову не упадёт через полтора-два месяца выложу игру)
@@sergeykazantsev1655 спасибо . . . но она вроде как на Unity, а гораздо больше юзеров работают с WinForms . .. поэтому желательно на них чтобы была игра.
Ну на WinForms я к сожалению не смогу написать игру, ибо я чистый юнитолог и пишу только на юнити :(
@@sergeykazantsev1655 если вы юнитолог тогда подскажите в какой среде разработки пишется обработка графики . . . с кодом понятно - визюал студия . . . а графика в какой среде пишется ? . . . и подскажите, пожалста, какое видео смотреть чтобы с нулю начать обучаться юнити
Юнити это и игровой движок и среда, в ней как в редакторе вы размещаете сцены и объекты и в ней же вы подключаете написанный код как некоторые компоненты. Насчёт курсов по юнити с нуля, затрудняюсь ответить, сам в свое время купил курс на юдемии 6 лет назад, но за это время многое могло измениться
di как паттерн ничего общего не имеет с принципом на буковку D, никто не запрещает в di использовать конкретные реализации и поставлять их клиентам, то что di может внедрять конкретную реализацию через интерфейс - это следствие. Был как раз упомянут service locator, там тоже всё прекрасно, есть инстансы, есть интерфейсы и при этом есть возможность получать зависимости в виде интерфейса, но проблема была в том, что в классической реализации локатора клиенту поставлялся этот мешок с зависимостями и не понятно было что в нём содержится, а если потребовать такой интерфейс выдаст ли он мне нужный и тому подобные вещи, а ведь буковка D соблюдается при таком подходе - никаких конкретных реализаций, только АБСТРАКЦИИ. Основное что нужно понимать, что в вашей программе при таком подходе появляется ровно одна точка, где разрешаются ЗАВИСИМОСТИ(не важно от чего, хоть от инта) и уменьшаете то количество мест, где вручную нужно было создавать и разрешать зависимости, с таким подходом вы пишете код и перестаёте думать о том как вам придёт эта зависимость или как и где её опрокинуть(хотя наивно так тоже думать), и тем самым вы больше добиваетесь прозрачности в потоке выполнения программы, чем пытаетесь соблюдать какой-то принцип на букву D. Поэтому с лёгкостью можно создавать фреймворки для таких штук, но также стоит упомянуть, что в таком случае вы отдаёте контроль над выполнением вашей программы другому инструменту, что также может сказаться в дальнейшем, но это уже больше детал
1. "DI как паттерн не имеет ничего с DIP" - к сожалению я с этим не могу согласиться. Очень важной частью DI как паттерна является внедрение зависимостей на уровне ИНТЕРФЕЙСА. А DIP говорит об организации кода на уровне АБСТРАКЦИЙ. Я уже не говорю о том что практически в любом материале по DI тебе в связку накидывают материалы про IoC который напрямую связан с DIP.
2. Можно ли использовать DI без интерфейса и прокидывать зависимости напрямую как конкретные реализации? Да, конечно. Но я бы разделял DI как паттерн(внедрение зависимостей на уровне интерфейса) и DI как простое прокидывание одних классов в другие. Если из DI убрать вот эту часть про "на уровне интерфейса" - что от него останется? Идея собирать зависимости в одном месте? А чем это отличается от какого-нибудь Entry Point да или тот же SRP можно сюда притянуть.
Лично я не считаю простое прокидывание одних классов в другие паттерном, так же как не считаю что Фабрика - это паттерн. Абстрактная фабрика или фабричный метод - это паттерны проектирования. Фабрика - это класс который порождает объекты но в этом нет никакого шаблонного решения. Возможно вкусовщина
@@sergeykazantsev1655 Понимаете, то что вы называете присуще и сервис локатору, основная идея DI, что у вас появляется точка конфигурации вашего приложения, вы не размазываете конфигурацию по приложению, а собираете всё в одном месте. Объект перестаёт думать о зависимостях и как ему их получить, он либо их разрешает сам, либо делегирует это слою инициализации приложения(что в народе и зовётся entry point). Насчёт последнего абзаца, это и есть основная идея DI, зависимости в классе настраиваются в каком-то инжекторе или контейнере или каким-то фреймворком. Класс перестаёт думать как ему получить ту или иную зависимость, он просто перестаёт отвечать за это. А программировать на уровне интерфейсов это уже совсем другое, если класс зависит от абстракции, то ему и поставим конкретную реализацию под абстракцией, которую если что можно поменять в той самой точке приложения и не думать, а в каких ещё местах надо было это сделать.
В общем, мне просто не нравится что в DI начинают рассказывать следствия этого подхода, DI - это про получение зависимостей, то что вы описываете в ролике это ioc-контейнеры. Тут же важно понимать само действие, классу их поставляют или он сам их запрашивает или вообще сам их создаёт.
И про первый пункт, в этом и проблема, что рассказывают про ioc контейнеры, а не про DI как таковой. Как и говорил выше, ioc контейнеры это конкретная реализация принципа DI, но не DI как таковой. Просто часто наблюдаю ситуации, когда говорят про DI и сразу ответ, а это какой-то ioc-контейнер с именем X.
@@sergeykazantsev1655 Также стоит упомянуть, что в ролике вы рассказываете про проблему сильной связности, DI к этому отношение не имеет.
Ну я думаю что мы друг друга услышали но остались при своём мнении. Наверное можно назвать это вкусовщиной потому что разные источники будут говорить разное.
С моей точки зрения ключевая идея DI именно в внедрении зависимостей на уровне интерфейса. Сервис локатор выполняет схожие задачи но несколько другим образом, вы это знаете
Я не считаю что главная идея DI - "у вас все зависимости должны быть собраны в одном месте". Таким местом(классом) в которым создаются зависимости EntryPoint-ы да и тот же сервис локатор на этапе инициализации.
Вообще DI нарушает приципы solid и создает огромную путаницу в коде. Чем больше зависимостей добавляешь тем хуже, странный подход со стороны продакшенов.
А в чем именно, с вашей точки зрения, di нарушает solid?
2,3,4 принципы. Так же, использование di и рядом не стоит с KISS/DRY. Так же, с моей точки зрения ООП идет нога в ногу с SOLID. Классы доджны быть инкапсулированы в ООП и иметь единую ответственность по SOLID?, но если мы будем что-то пытаться засунуть в закрытый класс, который должени иметь свою зону отвествунности через DI, мы просто теряем смысл писать на ООП и под SOLID. DI отлично в ECS системах впишется. А большинство потребностей "Внедрения зависимостей" решаются прямыми руками и использованием делегатов. Но может я и не прав.@@sergeykazantsev1655
Если у вас класс А содержит внутри себя класс Б, это не значит что он нарушает принцип единой ответственности. SRP он несколько про другое как мне кажется, он про то что методы, обрабатывающую одну конкретную сущность должны быть в одном месте. Это упрощает модификацию, так как не надо по всему проекту прыгать и про то чтобы организовать классы так, чтобы они не распухали.
Наоборот! DI это вся суть ооп и солида. Просто при прокидывании зависимостей ты должен привязываться не к конкретным реализациям, а к абстрациям. Это тебе дает огромную силу и гибкость. Процесс тестирования упрощается в разы.
Из геймдева примеры лучшие, самые понятные, когда наваливают веба ничего непонятно
Я как то приплетал di как устройство компа. Где есть центальная мать и шина которые следят за состоянием устройств и отвечают за взаимодейсивие и разграничивающая ответственность, которой нужно питание, для взаимодействия устройств, устройства это классы синглтоны копнув внутрь такого увидим почти целый мир рекурсивной реальности, похожую на мать диай шину, мироустройства разные/одановковые, все взаимодействуют как пчёлки гармонинчно, которые должны весьма специфично и в определенном порядке (билдеру привет) взаимодействовать. Можно накинуть планки оперативы, добавить pci ультрамеганавороченный класс вывода звука. Слой классов/устройств которые готовят для тебя финальную картинку (выходящие данные, собранный обьект приложения) на монитор. И слой классов внешних девайсов которые можно плуг/эн/плей подключать, отключать, не нарушая сомтояния всего компа. Ну тупо когда комп ребутает или умирает когда ты вставляешь или вынимаеш например флешку или подключаешь монитор.
А в итоге мы приходим в высшей абстракции - компьютер.
А компьютер ноутбук, десктоп, планшет, телефон??? Мироустройство матьегоети
Высшая абстракция это момент большого взрыва )