Comments 12
Данный модуль аудио считается конечным листом в графе зависимостей любой архитектуры, он не требует зависимостей ниже по графу, и ему не важно, кто его создает, но имеется ограничение: Этот модуль должен иметь стиль жизни “Singleton”
Так какой смысл городить весь этот огород, если все в итоге сводится к синглтону? Внутренности реализации, сам язык все-равно привязаны к конечной платформе / движку, какой смысл вводить дополнительные промежуточные абстракции?
Теперь, например, кнопки (или если угодно UIManager) используя методы, должны учитывать, к какому из каналов они относятся, фактически это опять возлагается на программиста.
А еще в исходном комменте было показано как эта работа снимается с программиста и выносится на визуальный уровень инспектора в редакторе (решает дизайнер, а не программист). Такой апи сделан для того, чтобы у программиста остался контроль за происходящим из кода.
+2
Так какой смысл городить весь этот огород, если все в итоге сводится к синглтону?
Вы наверное не правильно поняли понятие «стиль жизни Singletone». Есть разница между внедрением абстракций в управляющий код, и прямым указанием типа для получения ссылки из статического свойства. Во втором случае у вас появляется сильная связанность кода. Такой код поддерживать тяжело, а в случае если дерево синглтонов растет, то такой код зачастую называют «макаронами» и выбрасывают если необходимы существенные изменения. Стиль жизни Singletone же, в свою очередь, только лишь означает, что не нужно каждый раз создавать новый экземпляр при внедрении той же абстракции в разные части проекта.
Внутренности реализации, сам язык все-равно привязаны к конечной платформе / движку
А речь идет именно про перенос этого модуля из проекта Unity3D в проект Unity3D. Смена набора технологий это уже совсем другой разговор и статья к нему не относится.
А еще в исходном комменте было показано как эта работа снимается с программиста и выносится на визуальный уровень инспектора в редакторе (решает дизайнер, а не программист). Такой апи сделан для того, чтобы у программиста остался контроль за происходящим из кода.
Я понимаю, что у вас возможно было такое требование к продукту и мой коментарий именно к вашему проекту не применим. Но вы же согласитесь, что при написании такого апи для работы с SoundController риск человеческого фактора остался, пусть вы и переложили его на дизайнера? (Например: на ранней стадии проекта дизайнер реализует звук для кнопок, а через 2 месяца, забыв про это, делает звук перехода между меню в том же канале.)
И в геймдев пытается пролезть этот ынтерпрайз, печально...
Какое значение имеет эта строка поясните?
0
Во втором случае у вас появляется сильная связанность кода. Такой код поддерживать тяжело, а в случае если дерево синглтонов растет, то такой код зачастую называют «макаронами» и выбрасывают если необходимы существенные изменения.
Это называется — жестко формализованное апи, которое пишется один раз и редко меняется (библиотечная реализация без привязки к проекту). Если апи меняется — что-то пошло не так на стадии проектирования.
Стиль жизни Singletone же, в свою очередь, только лишь означает, что не нужно каждый раз создавать новый экземпляр при внедрении той же абстракции в разные части проекта.
Это называется не «стиль жизни», а всего лишь lazy initialization, которая применима не только к этому «стилю жизни». Синглтон он и в африке синглтон, тонкости реализации находятся за пределами понятия паттерна поведения.
риск человеческого фактора остался, пусть вы и переложили его на дизайнера?
А риск есть всегда, в вашем случае — полное отсутствие контроля за количеством параллельных звуков и возможность спаунить подсистемы звука, хотя они должны быть единственными (ошибки со стороны программиста зачастую не менее часты, чем со стороны дизайнера). В моем случае — хоть убейся, но не сможешь наделать дублей, придется исхитрятся и втискивать свои хотелки в жесткие ограничения (дохлое железо скажет спасибо, а точнее, расширившаяся аудитория бюджеток).
Какое значение имеет эта строка поясните?
habrahabr.ru/post/141477
megamozg.ru/post/4226
Не нужно делать что-то заведомо более сложным способом, чем его можно сделать. Сложность нужно добавлять по мере необходимости, иначе будет очередное монструозное решение с соответствующим высоким порогом входа на использование + повышение требований к железу для исполнения. Ынтерпрайз — это много абстракции, расширяемость важнее чем скорость исполнения конечного кода. GameDev — диаметрально противоположная область. Но это все IMHO, каждый пишет как хочет, это его право.
+2
А риск есть всегда, в вашем случае — полное отсутствие контроля за количеством параллельных звуков и возможность спаунить подсистемы звука, хотя они должны быть единственными
Защититься от дублирования AudioListener в моем случае, не так сложно, и это так же можно инкапсулировать внутри модуля. (Как пример: добавив не публичную статику.) Это уже опционально, так как при регистрации в контейнере этот кейс уже исключается. Действительно, каждый пишет как хочет и это рождает полезные дискуссии.
Под lazy Optimization мы имеем в виду одно и тоже, это так — Отложенная инициализация. И в вашем и в моем случае объект инициализируется непосредственно перед первым обращением к нему. Однако в статье я использую другой термин.
В области действия отдельного компоновщика компонент с жизненным стилем
Singleton ведет себя подобно паттерну проектирования Singleton, но структурно
ситуация отличается. Всякий раз, когда потребитель запрашивает компонент, по-
дается один и тот же экземпляр.
Но на этом сходство заканчивается. Потребитель не может получить через ста-
тический член доступ к зависимости, находящейся в области видимости Singleton,
и если мы запросим экземпляры у двух разных компоновщиков, мы получим два
разных экземпляра
(Внедрение зависимостей в .NET Марк Симан, стр 321, пункт 8.3.1)
0
Прололжая тему аргументированных усложнений, хотелось бы уточнить по поводу двух вопросов (изначально хотел написать только про тестирование, но ваш ответ выше наводит на ещё одну мысль).
Касательно Singleton lifestyle — в каком случае, по вашему мнению, вы сможете использовать два или несколько разных компоновщиков в вашей игре и чем это может быть оправдано? (компоновщик — это Container, если я правильно понял по контексту?)
И собственно вопрос по тестированию — в статье вы несколько раз приводите доводы касательно удобства тестирования отдельных модулей. Как в вашем случае происходит процесс тестирования? Что конкретно и каким образом вы тестируете в «модуле» Sound Manager?
Касательно Singleton lifestyle — в каком случае, по вашему мнению, вы сможете использовать два или несколько разных компоновщиков в вашей игре и чем это может быть оправдано? (компоновщик — это Container, если я правильно понял по контексту?)
И собственно вопрос по тестированию — в статье вы несколько раз приводите доводы касательно удобства тестирования отдельных модулей. Как в вашем случае происходит процесс тестирования? Что конкретно и каким образом вы тестируете в «модуле» Sound Manager?
+1
Касательно Singleton lifestyle — в каком случае, по вашему мнению, вы сможете использовать два или несколько разных компоновщиков в вашей игре и чем это может быть оправдано? (компоновщик — это Container, если я правильно понял по контексту?)
Да, компоновщик — это контейнер. В области разработки игр мне ни разу не приходилось использовать более одного. Возможно, имеет смысл разгружать класс, в котором перечислены бинды, тк он должен знать обо всех namespace, классы которых используются и прятать часть биндов ниже по графу ссылок. Но из-за того, что придется создавать дополнительную зависимость от конкретного контейнера в нескольких местах вместо одного, я такого стараюсь не практиковать. Наша история перехода по контейнерам была такая: Strange — Zenject — Ninject.
в статье вы несколько раз приводите доводы касательно удобства тестирования отдельных модулей. Как в вашем случае происходит процесс тестирования? Что конкретно и каким образом вы тестируете в «модуле» Sound Manager?
Для тестирования мы используем набор: Editor Test Runner + NUnit + NSubstitude. Тесты к модулю лежат в директории модуля по доп пути Модуль/Tests/Editor/. Тесты пишем на то, что проверить вручную долго: Сохранение уровней звука и музыки + вкл\выкл между сессиями (сохранение в Prefs), тест значений по умолчанию, корректное использование данных из файла конфигурации, Чистка gameObjects после себя. В случае провала теста все равно чистимся с помощью атрибута с ITestAction из NUnit, если нужно. Вообще, в случае когда у вас и сверху и с низу интерфейсы у «модуля», то потенциально возможно покрытие 100%. CI у нас тесты не учитывает, Unity Cloud Build недавно вынес этот функционал и беты (если вывел), запускаем в ручную до сливания feature и после.
Тесты не делаем для классов, которые уже непосредственно работают с MonoBehaviour или ScriptableObject, так как тесты могут выполнятся долго и стараемся эту прослойку между кодом и юнити делать как можно тоньше.
Некоторые модули пишем по TDD, но для нас это скорее треннинг, чем парадигма.
0
Синглтон он и в африке синглтон, тонкости реализации находятся за пределами понятия паттерна поведения.
В области разработки игр мне ни разу не приходилось использовать более одного.
То есть по большей части предыдущий комментатор прав, и такой синглтон будет реальным синглтоном, только без глобальной точки доступа? Это оправдано тем, что в вашем проекте DI используется повсеместно, и этот Sound Manager просто удобно вклинить в уже поднятую систему? Или пришлось поднять DI ради Sound Manager? Потому что в случае отдельного заимствования вашего Sound Manager из гитхаба, придётся тащить все интерфейсы и «поднимать» DI в проекте, где его может не быть, либо ради простоты делать ту же глобальную точку доступа. По моему мнению, у вас получился не столько пример Sound Manager, сколько пример использования DI в Unity проектах на примере Sound Manager.
+1
и такой синглтон будет реальным синглтоном, только без глобальной точки доступа
В этом я и вижу основной минус паттерна и его ключевую оссобенность. Я не могу сказать что вы сравниваете термины одного уровня. Глобальная точка доступа и есть его недостаток. Хуже становится не Sound Manager'у, а классу, которому приходится содержать в себе указание типа. Попробуйте написать на него тест.
Это оправдано тем, что в вашем проекте DI используется повсеместно, и этот Sound Manager просто удобно вклинить в уже поднятую систему?
Если вы просто передаёте ссылки вниз по дереву зависимостей через конструкторы, это тоже DI, и Sound manager будет работать без контейнера (Если передавать только через конструктор, то у вас в любом случае получится дерево, без связей «многие ко многим») В нашем случае да, мы стараемся не держать конструкторы громоздкими и часть ссылок проставляем контейнером. Множество других модулей используются у нас в том же ключе, и это решается у нас на этапе планирования архитектуры. У нас проектов много, и такая мобильность «модулей» для нас — дополнительное преимущество.
Потому что в случае отдельного заимствования вашего Sound Manager из гитхаба, придётся тащить все интерфейсы и «поднимать» DI в проекте, где его может не быть, либо ради простоты делать ту же глобальную точку доступа.
Интерфейсы Sound Manager'а — это часть модуля, апи для работы с ним. Они и должны храниться в том же namespace, и передаваться в субмодуле. Если вам не хочется использовать DI, но вы хотите сохранить слабую связанность, то тогда хотя бы используйте для получения ссылки на интерфейс Service Locator. Это рекомендация, и конечно, IMHO.
По моему мнению, у вас получился не столько пример Sound Manager, сколько пример использования DI в Unity проектах на примере Sound Manager.
Согласен, я сделал в статье упор именно на рефакторинг, и эти рекомендации применимы и к другим частям приложений. Я считаю правильным, написать о том что способ из реф статьи так же влечет за собой пассивное ухудшение качества кода проекта, и предложил способ как этого избежать. Я сам лично видел раз 5 код проектов, которые выросли из домашней разработки в нечто ценное, и затем тонули в бесконечном рефакторинге при первой же смене требований к продукту, из-за такого использования Singleton, по большей части.
0
Не могли бы подробней описать один из подобных случаев, когда подобный Singleton с глобальной точкой доступа портил возможность адекватного рефакторинга? Это не наезд, мне действительно интересно прочитать про этот опыт.
0
Если коротко: Для рефакторинга каждого синглтона в проекте вам придется так же модифицировать все классы, которые использовали его. И вместо того чтобы рефакторить класс как черный ящик, или модуль с устаканившимся апи, вам придется менять логику во множестве классов сразу. Один из неприятнейших моментов при этом — если есть синглтоны, значит нет тестов — значит при изменении класса ни кто не гарантирует, что все будет работать — чем больше классов подверглись изменениям, тем больше багов потенциально получится. В добавок, ситуацию усложнит получение данных через вложенные аксессоры. Чем больше вложенность, тем быстрее в команде выявить программиста для увольнения. Если у вас в коде несколько аксессоров, первый из которых статический, то вам придется решить сложную архитектурную задачу уже для множества классов, а не для 2х. Как пример:
Потребителю с таким обращением при рефакторинге не просто будет необходимо решить как получать ссылку на SoundManager, а еще и принять решение о достаточности полномочий для управления таким глубоким объектом AudioProcessor.
var ref = SoundManager.Instance.LastAudioProcessor.DoSmth();
Потребителю с таким обращением при рефакторинге не просто будет необходимо решить как получать ссылку на SoundManager, а еще и принять решение о достаточности полномочий для управления таким глубоким объектом AudioProcessor.
0
Проблема хорошо разбирается в:
Refactoring to Patterns (Addison-Wesley Signature Series)
Joshua Kerievsky
Глава 6 (на сколько я помню)
Там написано куда понятнее, чем я бы смог сформулировать.
Refactoring to Patterns (Addison-Wesley Signature Series)
Joshua Kerievsky
Глава 6 (на сколько я помню)
Там написано куда понятнее, чем я бы смог сформулировать.
-1
Промахнулся, перенес выше.
0
Sign up to leave a comment.
Альтернативный Sound Manager для мелких и средних проектов на Unity3D