Как стать автором
Обновить

Комментарии 12

А если по ходу игру нужно инстанциировать какой-нибудь объект с монобехом, который должен реагировать на паузу игры? Как в него прокинуть GameMachine, чтобы вызвать GameMachine.AddListner(this)? Получается какая-то циклическая зависимость.

Привет! Просто делаешь фабрику для этого объекта и в момент спауна подключаешь к системе GameMachine.AddListner(instantiatedObject)

Раз уж у GameMachine нет интерфейса, и значит мокать для тестов его не планируют, то не проще ли вместо кучи фабрик(а в фабрики ведь тоже нужно как-то этот GameMachine прокидывать) сделать этот GameMachine синглтоном, ну или получать его через какой-нибудь сервис локатор?

Вообще, даже для текущей реализации напрашивается улучшения - если количество ивентов у GameMachine останется таким же, то логично уйти от кастов типов, и сделать List<IFinishGameListener> _finishGameListeners и т. п.

Ну и обращение к приватным методам и переменным через this. - режет глаз. Понятно, что это вкусовщина, и можно даже найти какие-то аргументы за использование this, но так не принято. Лучше придерживаться более распространённых конвенций в обучающих статьях.

  1. Интерфейс будет в третьей части, когда причесывать будем, синглтона не будет)

  2. Можно сделать для каждого интерфейса свой лист List<IFinishGameListener>. Почему я же так не сделал, просто эти события старта и завершения будут вызываться один раз, поэтому тут больше выигрыш в памяти чем в производительности, потому что каст дешевый

  3. this — вкусовщина. Писать его или нет — зависит от конвенций в проекте/команде

Не могли бы вы в следующей статье уделить больше внияния сравнению разных видов архитектур? Ладно зенжект, допустим мы не хотим тащить di-фреймворк в проект, но и без него можно композишен рут организовать, вынести модель из монобехов оставив там только представление и события движка, сервисную модель организовать и т.д. Вы как-то сходу в карьер начали мемную архитектуру на монобехах задвигать, сделайте шаг назад и объясните почему так, в чем преимущества и ограничения этого варианта (они конечно есть как и у любого решения в программировании) по сравнению с другими подходами ну хотя бы обзорно. Опять же было бы неплохо рассказать зачем вообще нужна архитектура, кубики ж можно и без неё двигать, на какие важные вопросы она должна давать ответы, где и какие профиты даёт с точки зрения разработки, где и за счёт чего экономия/ускорение разработки происходит. Ну и т.д.

Статья стала бы гораздо интереснее, если бы архитектура явно решала бы какие-то задачи, а не "как двигать кубик но сделаем вид что мы архитекторы". Тогда хотя бы можно было обсуждать, хорошая она или нет и по каким причинам.
Надеюсь, на курсах вы хотя бы объясняете pros & cons тех или иных подходов, а не также беспричинно строите воздушные замки...

Давайте предположим, что мы делаем игру, где управляем кубиком с помощью клавиатуры.

В нашем деле очень важную роль играет контекст. Если он именно такой, то в глазах новичка это все будет выглядеть излишним. Он может заставить двигаться куб в одном скрипте, зачем идти такими огородами?

Не было контекста кроссплатформенности (что сразу лишает нас необходимости вводить какую-либо прослойку ввода), не было инстанса объектов в рантайме, сохранений/загрузки и т.д.

То есть вместо видения игры, как архитектуры, нам просто предлагают реализовать отдельную маленькую механику, при том сама реализация слабая. Любой инстанс сломает эту систему, потому что observable регистрирует только то, что заранее лежит на сцене.

Да и порядка инициализации не появилось, о котором заявляется в самом начале, игра стартует из контекстного меню, серьезно?

Если в игре будет больше одной сцены, то при таком подходе получится запуститься сразу со второй? Вряд ли! Придется таскать по всем сценам инсталлер, его зависимости, игрока и все остальное.

Извините, но, когда сеньор с 5-летним опытом выдает архитектуру на монобехах, то ценность статьи падает до нуля.

Привет! Спасибо большое за фидбэк)

Я же написал, что будет продолжение :)

В первой части я показал, что обязательно нужно сделать механизм обработки событий (на монобехах он или нет — не суть важно)

Во второй части будем уходить от зависимостей и внедрять простой DI

В третьей — сделаем оптимизацию архитектуры по памяти и производительности: уйдем от монобехов и повысим производительность в апдейтах. Там же прикрутим порядок инициализации

Понимаю, что по первой части не все понятно, есть вопросы, и архитектура реально выглядит мемной ? Но в конце статьи увидите, что из этого получиться :)

Просто статья большая, и я не могу ее всю сразу выкатить, поэтому пока так, по кусочкам. Надеюсь, что развеял ваши сомнения)

Ну, да. Первая часть довольно смешная в плане архитектуры, тогда подожду конец статьи, чтоб лучше понять замысел

Есть вопросы по коду.
1) обычно, считается, что Observer/Listener используется как противовес подпискам/отпискам на события. В приведенном коде MoveController сразу используется Listener, так и подписки внутри событий листенеров. Почему не использован единый подход с Listener'ами?
2) Зачем разделять IStartGameListener и IFinishGameListener? Не говоря уже про паузу. Есть ли две разные причины (как минимум) для изменения поведения в старте и финише? Этим точно будут заниматься разные объекты?
3) private readonly List<object> listeners = new(); - почему тут object? У Вас неопределенное количество возможных вариантов (интерфейсов) Listener'ов? Кажется, что оно строго определено, как минимум методами внутри класса. Например, внутри класса нет метода AddCoin, поэтому класс не может работать с ICoinAmountChangeListener. Кроме того в использовании object нет смысла еще и потому что AddListener не является членом интерфейса, а если бы являлся, то больше смысла было бы в том, чтобы сделать его generic. Применимо к данному примеру лучше было бы сделать несколько перегрузок и несколько списков private readonly List<IStartGameListener> startListeners = new(); private readonly List<IFinishGameListener> finishListeners = new(); и т.д.



От private readonly List<object> listeners = new(), стоит отказаться как минимум по причине неоднократной распаковки, из object в объект нужного нам типа, также прочитал некоторую оптимизацию использования операторов is и as, из книги Рихтера, вот выдержка из нее.


{

Для null-ссылок оператор is всегда возвращает false, так как объекта, тип

которого нужно проверить, не существует.

Обычно оператор is используется следующим образом:

if (o is Employee) {

Employee e = (Employee) o;

// Используем e внутри инструкции if

}

В этом коде CLR по сути проверяет тип объекта дважды: сначала в операторе

is определяется совместимость o с типом Employee, а затем в теле оператора if


анализируется, является ли o ссылкой на Employee. Контроль типов в CLR укре-

пляет безопасность, но при этом приходится жертвовать производительностью,


так как среда CLR должна выяснять фактический тип объекта, на который ссы-

лается переменная (o), а затем проверять всю иерархию наследования на предмет


наличия среди базовых типов заданного типа (Employee). Поскольку такая схема

встречается в программировании часто, в C# предложен механизм, повышающий

эффективность кода с помощью оператора as:

Employee e = o as Employee;

if (e != null) {

// Используем e внутри инструкции if

}

В этом коде CLR проверяет совместимость o с типом Employee. Если o и Employee


совместимы, as возвращает ненулевой указатель на этот объект, а если нет — опера-

тор as возвращает null.

}

Спасибо) надо будет все-таки прочитать больше, чем 3 главы))) Комментарий в тему, но я даже без таких подробностей не понял, зачем используеутся List<object>... Ответа, наверное, мы не получим.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий