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

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

>в официальном уроке Unity3D SoundManager
Дизайн API юнити это наоборот отрицательный пример. У них даже не было одной конвенции наименования, чего уж говорить о хорошем дизайне API. Положительный пример это XNA.

>То, что класс имеет глобальную точку доступа
Всё на этом можно закрывать обсуждение. Объект имеющий состояние и имеющий общую точку входа, это непредсказуемый с точки зрения состояния объект с непредсказуемым количеством связей. Точка после который продукт становится поделием.

Синглтон это точка бифукации в карьере программиста. После прохождения этой точки становится понятно качество продукта, которое из него будет выходить.
Бедный Brackeys с его «недотуторами». Не эволюционировал видимо ещё. Повторяюсь, я не призываю плодить Синглтоны, но порой они оказываются удобными. Да и сама работа с Unity наталкивает на их использование очень часто. Надеюсь теперь меня за ссылку не побьют модеры.
blogs.eae.utah.edu/jkenkel/why-i-overuse-the-singleton-pattern-in-unity
Как категорично то. По-моему, точка невозврата в профессиональном развитии разработчика — это когда он начинает обвинять других разработчиков в чём-то, не разобравшись в вопросе спора сам. У синглтона есть своё применение.
Я готов вступить в аргументированный спор. И даже предоставил пару начальных аргументов:
> это непредсказуемый с точки зрения состояния объект с непредсказуемым количеством связей

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

И «апеллирование к тону» конкретно от вас не являются аргументом.
> У синглтона есть своё применение.
Я не писал, что у него нет применения. Некоторые люди делают целые проекты на синглтонах. Многие вставляют вместо застежки ремня безопасности заглушку, что бы бортовая система автомобиля не ругалась на непристегнутого водителя. У заглушки ремня безопасности есть применение.
Вот я глупый наверное. О каком количестве связей идёт речь? У меня есть class ItemGenerator к которому я получаю доступ только при открытии сундуков или при получении лута с моба. Где возьмутся остальные связи?

Проблем нет, если новых фич/кода не предвидется.
Проблемы начинаются, когда вы, например, придумаете повторяемые квесты с рандомным дропом, захотите сделать особенный лут для какого-нибудь ивента(например повысите вероятность получения вещей в 2 раза), а потом придумаете, что не только должна повышаться вероятность получения вещей, но и в некоторых случаях их количества(например 2 зелья здоровья, вместо одного, 20 монет вместо 10 и т.д.), или вероятность выпадения "уникального хэллоуинского/новогоднего наряда", а потом ещё и доп. лут к 23 февраля и 8 марта...


Это одна из настоящих проблем Coupling'а(сильной связанности). Если вы будете писать без DI(Dependency Injection, внедрения зависимостей), то вам придётся писать огромные if'ы и ваш элегантный код превратится в макаронный, а с DI проблем с синглтонами нет, потому что вы сможете сделать под каждый случай свой синглтон. Например сделаем interface LootManager и следующие наследники:


  1. FixedLootManager — для дропа из квестовых монстров и сундуков из "кампании". Бросает ровно то что нам нужно.
  2. RandomLootManager — ScriptableObject, который генерирует случайный лут для уровня данного уровня монстра
  3. LevelDependentLootManager — SO, который генерирует лут в зависимости от уровня героя
  4. HighierRatesLootManager — SO для генерации лута с увеличенными рейтами(шансом выпадения).
  5. HalloweenSpecialLootManager — SO для генерации лута для хэллоуинских ивентов

и т.д.
Согласитесь, это выглядит получше.

Согласен полность. И пример ваш мне очень понравился, достаточно ясный. Но ваш посыл в том, что LootManager разрастётся до GOD-класса. Мб я не так понимаю что есть бог? И я не говорил про весь лут, я говорил лишь о генерации итемов. Хотя мы всё равно ведём разговор абстракциями. То есть у меня есть броня\оружие. Дальнее и не очень. Разрастаться ему особо некуда.

И лично я как раз стараюсь разбивать задачи когда понимаю, что код нужен расширяемый. Но опять же повторю. Проектирование решает все проблемы.
это непредсказуемый с точки зрения состояния объект с непредсказуемым количеством связей
Поправлю. Синглтон — это все-таки не объект, а шаблон проектирования. И суть его в общем случае не в гарантии единственности экземляра своего класса, а в гарантии единственности экземляра некоторого класса. «Свой класс» — это частный случай, и, как правило, именно такое использование синглтона вызывает непредсказуемость.
А поглядите, например, на совместное использование шаблонов синглтон + фабрика. Тут даже с единой ответственностью все в порядке.

В общем синглтон обманчиво прост, подводные камни есть, но Suvitruf прав — готовить его можно.
Поправлю. Синглтон — это все-таки не объект, а шаблон проектирования.

Даже больше, это шаблон времени жизни объекта, и к прямого отношения к тому что сейчас обсуждается не имеет, только косвенное. Тут обсуждается глобально доступный объект, который сам управляет своим созданием/доступом и имеет состояние.


Сферический:


class Manager
{
    public static Manager Instance = new Manager();
}
Тут обсуждается глобально доступный объект, который сам управляет своим созданием/доступом и имеет состояние. Сферический
Благодарю за уточнение, если речь только об этом, то я с вами совершенно согласен.
Одни «за», другие «против».
Хотелось бы узнать, бывали ли у кого случаи когда они делали приложение с синглтонами, а потом сильно пожалели об этом?

Я боюсь, что это любой проект, который со временем обрастает кодом:
Сложности тестирования делают ваш код менее стабильным, а наличие God-Object'а и вовсе может привести к сложно обноруживающимся багам.
Более подробный ответ об проблемах и "альтернативах" я описал ниже.

Also, если наша игра требует связей, то еще раз пересматриваем архитектуру.
Обиженные «тру SOLID'ы» побежали мне карму минусовать. Вы хоть комментируйте, я же так опыта не наберусь!

Мой "основной" язык — Java, так что расскажу как дела обстоят там и почему правы и те люди, которые говорят что синглтон зло, и почему правы вы:


Нарушение принципа единственной ответственности

В Java (и скорее всего в C#) синглтон создает сам себя, хранит сам себя и делает свою бизнес логику. Это ужасно, это отвратительно, это плохо.
НО! В Unity этим занят сам движок. Вы только делаете ScriptableObject и сам движок управляет жизненным циклом синглтона. Про "God Object" поговорим дальше, пока считаем что эта проблема решена.


Singleton невозможно тестировать
Тесная связь

Эти проблемы связанны напрямую. Coupling(т.н. "связывание", оно же "сильная связанность") — это когда объект содержит другой объект. В итоге мы не может протестировать их по одиночке. Юнит тестирование класса аггрегирующего другой класс будет невозможно, т.к. мы будем завязаны на использование внутреннего объекта. Это тоже ужасно, отвратительно и плохо
Но опять нам на выручку приходит игровой движок, который по совместительству выполняет роль Dependency Injection(внедрение зависимостей, DI).
Многие начинающие программисты на юнити используют его даже не подозревая этого, потому что оно настолько простое. Перетаскивание объектов в скрипты, Изменение их значений через UI — всё это внедрение зависимостей и эта простота не уменьшает возможности.
Хотите синглтон(ScriptableObject)? Пожалуйста! Но будет гораздо разумнее использовать наследование и встроенный DI.
Примеры:


  1. Сделать разные свойства игры для разных сложностей: сделаем игру тетрис, три разных ScriptableObject'а описывающих разные значения высоты и ширины поля и скорости игры. В зависимости от выбранных настроек инжектим разные ScriptableObject'ы.

  2. Сделать разные характеристики для юнитов в стратегии, для последующего теста и менять их только изменяя SO при создании юнита.

  3. Сделать разные AI для разного поведения опонента в экономической стратегии: более агрессивный и более миролюбивый.


Каждый из приведенных выше примеров позволит протестировать разный ScriptableObject отдельно, и заменить их на Mock'и при тестировании классов, которые их используют.
При каждом изменении мы сможем заменить старый SO на новый, оставив старый, на случай "отката", если идея не взлетит.
Проблема с God-Object тоже пропадает при использовании DI.

Перечитал ещё раз свой комментарий и заметил, что из него плохо понятно чем плох первый пункт минусов синглтона(про единую ответственность).
Т.к. предыдущий коммент получился большой, решил написать отдельно:
Каждый класс должен выполнять ровно ту функцию, для которой он был создан. В случае с синглтоном тут две функции: управление своим жизненным циклом и "бизнесс-логика" вложенная в него.
Несмотря на то что "управление своим жизненным циклом" кажется простой задачей, всё становится гораздо сложнее с многопоточными приложениями.
Наш стильный маленький "бойлерплейт"(повторяющийся код, необходимый для правильного выполнения программы) обрастает синхронизациями, и прочими вещами, которые осложняют понимание бизнес-логики кода и повышают шансы на то, что кто-то ненароком его сломает.

Примерно поэтому я и указал, что говорю исключительно о Unity. Я не притендую на 100% истинность, но Unity — это не для бизнес приложений. Там отсутствует многопоточность (да, это решается с помощью реактивного программирования, но всё же). А когда люди уходят от Синглтонов, они идут в сторону Инжектора Зависимостей. Что на мой взгляд оставляет проблему тесной связи, но в другой обёртке.

Про тестирование вообще отдельный разговор. Говорил с одним владельцем конторы по тестированию игр. Тот сказал «Ты правда веришь в то, что всю систему можно обложить Unit-тестами?». Опять же, не говорю не тестировать. Но баги будут всегда.
А когда люди уходят от Синглтонов, они идут в сторону Инжектора Зависимостей. Что на мой взгляд оставляет проблему тесной связи, но в другой обёртке.

Связь остается, но она уже не тесная, что имеет большое значение. Выше уже был пример про внедрение разных LootManager'ов.
И ещё. edge790. Вы описали 3 примера, которые подходят под мою фразу о проектировании. Unity богата на префабы. И идеальным вариантом будет описывать поведение объектов, внутри их самих. А вот уже то где генерировать, когда генерировать и с какими параметрами — можно переложить на Singleton.
Перед обсуждениями за и против, я рекомендую прочитать вот эту главу прекрасной книги
live13.livejournal.com/467905.html

На мой взгляд больше проблема не в самом синглтоне, а в непонимании того что, есть depency injection, который мб и менее очевиден на первый взгляд, но решат все проблемы которые решает и порождает синглтон
Хочется спросить, а чем плохо использовать Singleton по аналогии с DI? Для Unity3D это очень удобный подход. Создаем класс GameManager и прописываем в нем ссылки на нужные объекты (классы), после чего создаем объекты (классы).

Например:
<code>
public GameSettings GameSettings { get; private set; }
public ConfigController ConfigController { get; private set; }
public KeyboardController KeyboardController { get; private set; }
public IFormDetector FormDetector { get; private set; }
public DepthTracker DepthTracker { get; private set; }
public GameController gameController { get; private set; }

void Start ()
{
    GameSettings = Resources.Load("GameSettings") as GameSettings;
    ConfigController = this.transform.GetOrAddComponent<ConfigController>();
    KeyboardController = this.transform.GetOrAddComponent<KeyboardController>();
    FormDetector = this.transform.GetOrAddComponent<PixelCompare>();
    DepthTracker = this.transform.GetOrAddComponent<DepthTracker>();
    gameController = this.transform.GetOrAddComponent<GameController>();
}
</code>


А если ещё и добавить puplic static с возвратом объекта(класса), то можно получить ещё и короткие ссылки.

Например:
<code>
public static GameController GetGameController()
{
    return GameManager.Singleton.gameController;
}
</code>


Вот и получилось элегантное решение и возможно вызвать в любой момент GameManager.GetGameController().
Поздравляю с изобретением паттерна Service locator! Осталось заменить геттеры конкретных классов на общий геттер интерфейсов и получится Dependency Injection.
Кроме того, получится велосипед. Зачем делать свое подобие DI-фреймворку, если уже есть готовые и более продвинутые решения? Разве что вам нужно реализовать какие-то специфичные фичи, которые в готовых фрейворках никак не сделать.
1. Да я согласен про велосипед, но речь идет о применении Singleton.
2. Как я понимаю мы обсуждаем работу в рамках Unity3D и желательно видеть результат в инспекторе, а не просто выводить в лог.
3. Данный код позволяет повесить класс на объект и управлять классом через инспектор, а то что предлагаете Вы, не позволяет сделать визуальный дебаг класса.
Я так понимаю вы просто не согласны с этой статьёй: habrahabr.ru/post/270005. А также Вы исключили возможность использовать данный пример кода как единую входную точку. Попробуйте оценить этот код с точки зрения: инициализация самого проекта.
Если говорить про данный код
Что будет если кто-то будет требовать менеджеры в Awake/OnEnable?

Если говорить в общем
Пример — у нас мультиплеер-шутер и в данном случае мне нужно чтобы игрок мог менять контрол и аудиоменеджер, т.е. чтобы в рантайме я мог заменить GameController c обычного на 'Spectrator' и у него должен быть другой AudioManager, в котором мы можем слышать все переговоры, а не только своей комманды, т.е. я бы хотел иметь возможность делать еще один инстанс GameMode, а это решение мне такой возможности не дает и заставляет просто прописывать всё это во внутрь самих менеджеров
Идея данного кода состоит в том, что он срабатывает первым и создает окружение вокруг себя, это своего рода единая точка входа. Соответственно все что сработает в Awake/OnEnable получит доступ к данному коду.
Спасибо за интересный текст и не менее интересные комментарии:)
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории