Альтернативи одиночним / глобальним


16

Я безліч разів чув про підводні камені синглів / глобалістів, і розумію, чому вони так часто нахмурюються.

Я не розумію, що це елегантна, безладна альтернатива. Здається, що альтернатива використанню одиночних / глобальних завжди передбачає проходження об'єктів на мільйон рівнів вниз через об'єкти вашого двигуна до тих пір, поки вони не дойдуть до об'єктів, які їм потрібні.

Наприклад, у своїй грі я передзавантажую деякі активи, коли гра починається. Ці активи не використовуються набагато пізніше, коли гравець переходить через головне меню та входить у гру. Чи повинен я передавати ці дані з мого об'єкта Game, в мій об'єкт ScreenManager (незважаючи на те, що лише один Екран насправді піклується про ці дані), потім у відповідний об’єкт Screen та деінде?

Просто здається, що я торгую глобальними даними про стан для вбудованої залежності, передаючи дані об'єктам, які навіть не цікавляться цими даними, за винятком цілей передачі їх дочірнім об'єктам.

Це той випадок, коли Сінглтон був би хорошою справою, або є якесь елегантне рішення, якого я відсутній?

Відповіді:


16

Не плутайте одинаків і глобалів. Хоча зазвичай потрібні якісь глобальні змінні, синглтон - це не просто заміна глобальної змінної, а в першу чергу спосіб вирішення проблем порядку статичної ініціалізації в C ++ ( і FQA ). (В інших мовах це спосіб подолати різні мовні недоліки, як, наприклад, відсутність глобальних змінних та голових функцій.)

Якщо ви можете просто використовувати глобальний покажчик замість синглтона і переконайтеся, що він ініціалізується (вручну) перед тим, як щось потребує, ви уникаєте виклику функції та накладення гілки, кульгавого синтаксису, щоб потрапити на об’єкт, і ви можете фактично зробити другий екземпляр класу, коли вам потрібно пройти тести або тому, що ваш дизайн змінився.

Для кількох глобальних змінних, які ви хочете (загальні приклади - аудіовиведення, список відкритих вікон, обробник клавіатури тощо), я рекомендую схему локатора обслуговування . Це дозволяє легко замінювати речі різними реалізаціями (наприклад, реальне проти нульового аудіопристрою), і збирає всі ваші глобальні ресурси в одну структуру, щоб уникнути забруднення вашого простору імен.


+1. Хороша відповідь та спасибі за посилання шаблону послуги локатора. Це дуже цікаве прочитання.
bummzack

1

Якщо ви не можете / не можете мати одну частину коду, магічно "знайте" про деякі дані, то це потрібно буде якось передати. Однак це не означає, що він обов'язково повинен передаватися лише аргументами.

У вашому прикладі випадку, чи не могли б ви мати якийсь "AssetManager", який би завантажував і зберігав активи, і тоді ScreenManager потрібно було б лише вказати на це (можливо, під час створення)? У цьому сенсі ви передаєте посилання на активи, загорнуті в інший об'єкт, і ви можете передавати це один раз, при ініціалізації, а не передавати його до функції аркуша, коли це потрібно.

Тепер IMHO, що AssetManager, будучи видом речі, яку ви хочете лише одного, може також стати сингтом. За умови, що ви розумієте підводні камені та кодуєте спеціально, щоб їх уникнути (припустимо, що однократний доступ буде доступний одночасно з декількох потоків та забийте себе виделкою щоразу, коли ви зробите щось, що потрібно блокувати), а потім вибийте себе.


1

Я думаю, що Джейсон Д абсолютно має рацію - ось як я би впорався з цим:

У грі є примірник AssetManager, об’єкт, з якого ви можете отримати будь-який актив за назвою.

У грі:

assetManager = new AssetManager();
screenManager = new ScreenManager();
screenManager.assetManager = assetManager;

У ScreenManager:

screen = new Screen();
screen.assetManager = assetManager;

На екрані:

myAsset = assetManager.getBitmp("lava.png");

Тепер усі екрани мають доступ до будь-яких необхідних їм активів. Це не є більш складним або божевільним, ніж використання глобальних або Singletons, і у вас є можливість мати два екземпляри гри в одній програмі без сутичок. Мені колись довелося зробити гру, що складалася з 8 міні-ігор, усі мали спільні базові класи / рамки. Мені довелося перефактурувати всі мої глобули / одинаки, щоб використати цей стиль опорного проходження, і я ніколи не оглядався. Єдині речі, які мають бути глобальними, - це речі, які фізично можуть існувати лише один раз, такі як аудіо, мережа, введення / виведення тощо.


0

Ви можете використовувати заводський зразок для заміни Singleton . Тоді фабричний клас має контроль над тим, скільки екземплярів ви можете створити, які ви можете легко змінити пізніше, коли виявите, що вам потрібно більше одного AssetManager. Як зазначено в цій статті :

Це дає вам всю гнучкість Сінглтона, ніде так багато проблем.


Інша, досить обмежена, можливість - зробити клас статичним (що, на мою думку, не є можливим для AssetManager і можливе лише на мовах, які взагалі мають статичні класи). Але це працює лише в тому випадку, якщо вам не потрібен спадок / поліморфізм. Це дуже негнуче рішення:

статичні методи такі ж гнучкі, як граніт. Кожен раз, коли ви користуєтесь ним, ви кидаєте частину своєї програми бетоном. Просто переконайтесь, що у вас не застрягла нога, коли ви спостерігаєте, як вона затверділа. Колись ви здивуєтеся, що, на жаль, вам дійсно потрібна інша реалізація цього вищого класу PrintSpooler, і це повинен був бути інтерфейс, фабрика та набір класів реалізації. D'oh!

Йдеться про статичні методи, але їх можна застосовувати і до статичних класів.


Що ви маєте на увазі під "зробити клас статичним"? У C ++ немає статичних класів. Ви маєте на увазі лише статичні методи? Чому тоді турбуватися мати клас замість простору імен?

1
@Joe: Ну, питання не зосереджене на C ++, як я це зрозумів. У C # або Java ви можете робити статичні класи, і я посилаюся на них. Крім того, як я вже сказав, статичні класи не є оптимальним рішенням у більшості випадків, але в рідкісних випадках він може працювати як глобальний.
Майкл Клемент
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.