Як уникнути "менеджерів" у своєму коді


26

Наразі я переробляю систему Entity для C ++, і у мене є багато менеджерів. У своєму дизайні я маю ці класи, щоб зв'язати свою бібліотеку разом. Я чув багато поганого, коли мова йде про "менеджерські" класи, можливо, я не називаю свої класи належним чином. Однак я не маю уявлення, як ще їх назвати.

Більшість менеджерів у моїй бібліотеці складаються з цих класів (хоча це дещо відрізняється):

  • Контейнер - контейнер для об'єктів у менеджері
  • Атрибути - атрибути для об'єктів у менеджері

У своєму новому дизайні для моєї бібліотеки я маю ці конкретні класи, щоб зв'язати свою бібліотеку разом.

  • ComponentManager - керує компонентами в системі Entity

    • ComponentContainer
    • ComponentAttributes
    • Сцена * - посилання на сцену (див. Нижче)
  • SystemManager - керує системами в Entity System

    • SystemContainer
    • Сцена * - посилання на сцену (див. Нижче)
  • EntityManager - керує об'єктами в Entity System

    • EntityPool - пул сутностей
    • EntityAttributes - атрибути об'єкта (це буде доступно лише для класів ComponentContainer та System)
    • Сцена * - посилання на сцену (див. Нижче)
  • Сцена - зв’язує всіх менеджерів разом

    • ComponentManager
    • SystemManager
    • EntityManager

Я думав просто поставити всі контейнери / басейни у ​​сам клас Scene.

тобто

Замість цього:

Scene scene; // create a Scene

// NOTE:
// I technically could wrap this line in a createEntity() call in the Scene class
Entity entity = scene.getEntityManager().getPool().create();

Це було б так:

Scene scene; // create a Scene

Entity entity = scene.getEntityPool().create();

Але, я не впевнений. Якби я робив останнє, це означало б, що я мав би багато об'єктів і методів, оголошених всередині мого класу Scene.

ПРИМІТКИ:

  1. Система сутності - це просто дизайн, який використовується для ігор. Він складається з 3 основних частин: компоненти, сутності та системи. Компоненти - це просто дані, які можуть бути "додані" до об'єктів, щоб сутності були відмінними. Суб'єкт представлений цілим числом. Системи містять логіку для сутності, із конкретними компонентами.
  2. Причина, що я змінюю дизайн своєї бібліотеки, полягає в тому, що я думаю, що це можна змінити досить сильно, на даний момент мені не подобається відчуття / потік до нього.

Чи можете ви, будь ласка, детальніше розповісти про погані речі, які ви чули про менеджерів, і як вони стосуються вас?
MrFox


@MrFox В основному я чув те, що згадував Данк, і в моїй бібліотеці є багато класів * Менеджер, яких я хочу позбутися.
miguel.martin

Це також може бути корисним: medium.com/@wrong.about/…
Zapadlo

Ви виділяєте корисну основу робочого додатку. Написати корисну рамку для уявних додатків майже неможливо.
Кевін Клайн

Відповіді:


19

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

Як каже Данк, класи менеджерів називаються так, оскільки вони "керують" речами. "керувати" - це як "дані" або "робити", це абстрактне слово, яке може містити майже все.

Я здебільшого знаходився там же, що і ти, близько 7 років тому, і я почав думати, що в моєму мисленні щось не так, тому що докладати стільки зусиль, щоб кодувати поки нічого не робити.

Що я змінив, щоб виправити це, - це змінити словник, який я використовую в коді. Я повністю уникаю загальних слів (якщо це не загальний код, але це рідко, коли ви не робите загальних бібліотек). Я уникаю називати будь-який тип "Менеджер" або "об'єкт".

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

Ніколи не називайте тип "менеджер" і переконайтесь, що всі ваші типи мають єдину унікальну роль - моя рекомендація. Колись може бути важко знайти імена, але це взагалі найважче в програмуванні


Якщо пара dynamic_castвбиває вашу продуктивність, тоді використовуйте систему статичного типу - ось для чого це потрібно. Це справді спеціалізований контекст, а не загальний, що потрібно прокручувати свій власний RTTI, як це робив LLVM. Навіть тоді вони цього не роблять.
DeadMG

@DeadMG Я не говорив про цю частину зокрема, але більшість високопродуктивних ігор не можуть дозволити собі, наприклад, динамічні розподіли за допомогою нових або навіть інших матеріалів, які добре підходять для програмування взагалі. Вони є специфічними звірами для конкретного обладнання, яке ви не можете узагальнити, і тому "це залежить" є більш загальною відповіддю, зокрема з C ++. (ніхто в ігровому розробнику не використовує динамічний ролик, до речі, він ніколи не корисний, поки у вас немає плагінів, і навіть тоді це рідко).
Клайм

@DeadMG Я не використовую динамічний_каст, а також не використовую альтернативу динамічному_касту. Я реалізував це в бібліотеці, але не міг потрудитися вийняти його. template <typename T> class Class { ... };насправді - це призначення ідентифікаторів для різних класів (а саме користувацькі компоненти та спеціальні системи). Присвоєння ідентифікатора використовується для зберігання компонентів / систем у векторі, оскільки карта може не приносити потрібну мені гру.
miguel.martin

Добре, що я також не реалізував альтернативу динамичному_касту, а лише підроблений type_id. Але все-таки я не хотів, чого досягнув type_id.
miguel.martin

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

23

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

Почнемо з ObjectFactory . По-перше, функціонують покажчики. Це без громадянства, єдиний корисний спосіб без громадянства створити об'єкт - newі для цього вам не потрібна фабрика. Державне створення об'єкта - це те, що корисно, а покажчики функцій не корисні для цього завдання. По-друге, кожен об'єкт повинен знати, як знищити себе , не знаходячи саме цього створюючого екземпляра, щоб знищити його. Крім того, ви повинні повернути розумний вказівник, а не необроблений покажчик, за винятком та безпеці ресурсів вашого користувача. Весь ваш клас ClassRegistryData справедливий std::function<std::unique_ptr<Base, std::function<void(Base*)>>()>, але з вищезазначеними отворами. Це зовсім не потрібно існувати.

Тоді у нас є AllocatorFunctor - вже є стандартні інтерфейси розподільника. Не кажучи вже про те, що вони просто обгортки delete obj;та return new T;- що, знову ж таки, вже передбачено, і вони не будуть взаємодіяти з будь-якими стаціонарними або спеціальними розподільниками пам'яті, які існують.

І ClassRegistry просто, std::map<std::string, std::function<std::unique_ptr<Base, std::function<void(Base*)>>()>>але ви написали для нього клас замість того, щоб використовувати існуючу функціональність. Це той самий, але абсолютно непотрібний глобальний стан, що змінюється, з купою марень.

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

Тоді ми маємо Ідентифікуючу . Тут багато тієї самої історії - вона std::type_infoвже виконує завдання ідентифікації кожного типу як значення часу виконання, і це не вимагає надмірної котлової панелі з боку користувача.

    template<typename T, typename Y>
    bool isSameType(const X& x, const Y& y) { return typeid(x) == typeid(y); }
    template <class Type, class T>
    bool isType(const T& obj)
    {
            return dynamic_cast<const Type*>(std::addressof(obj));
    }

Проблема вирішена, але без жодного з попередніх двохсот рядків макросів і чогось іншого. Це також позначає ваш IdentifiableArray як нічого, але std::vectorвам доведеться використовувати підрахунок довідок, навіть якщо унікальне володіння чи невласник буде кращим.

О, і я вже згадував, що Типи містять купу абсолютно марних typedefів? Ви буквально не отримуєте жодної переваги перед використанням безпосередньо сировинних типів і втрачаєте хороший шматок читабельності.

Далі йде ComponentContainer . Ви не можете вибрати право власності, ви не можете мати окремі контейнери для похідних класів різних компонентів, ви не можете використовувати власну семантику життя. Видалити без докорів.

Тепер, ComponentFilter, можливо, вартує трохи, якщо ви його багато реконструювали. Щось на зразок

class ComponentFilter {
    std::unordered_map<std::type_info, unsigned> types;
public:
    template<typename T> void SetTypeRequirement(unsigned count = 1) {
        types[typeid(T)] = count;
    }
    void clear() { types.clear(); }
    template<typename Iterator> bool matches(Iterator begin, Iterator end) {
        std::for_each(begin, end, [this](const std::iterator_traits<Iterator>::value_type& t) {
            types[typeid(t)]--;
        });
        return types.empty();
    }
};

Це не стосується класів, похідних від тих, з якими ви намагаєтесь мати справу, але також і вашого.

У решті це майже та сама історія. Тут немає нічого, що не вдалося зробити набагато краще без використання вашої бібліотеки.

Як би уникнути менеджерів у цьому коді? Видалити в основному все це.


14
Навчання мені знадобилося певний час, але найнадійніші фрагменти коду - це ті, які там відсутні .
Tacroy

1
Причиною, що я не використовував std :: type_info, є те, що я намагався уникати RTTI. Я згадав, що рамки були спрямовані на ігри, що в основному є причиною того, що я не використовую typeidабо dynamic_cast. Дякую за відгук, проте я ціную це.
miguel.martin

Чи ця критика базується на ваших знаннях системних двигунів компонентних сутностей чи це загальний огляд коду C ++?
День

1
@Den Я думаю, що більше проблем дизайну, ніж будь-що інше.
miguel.martin

10

Проблема класів Manager полягає в тому, що менеджер може робити все, що завгодно. Ви не можете прочитати назву класу і знати, що робить клас. EntityManager .... Я знаю, що це щось робить з Entities, але хто знає, що. SystemManager ... так, він управляє системою. Це дуже корисно.

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


1
+1, і не тільки вони можуть зробити що-небудь, на досить довгій часовій шкалі вони будуть. Люди розглядають дескриптор "Менеджера" як запрошення створити кухонні мийки / швейцарські армії-ножі.
Ерік Дітріх

@Erik - Так, я мав би додати, що мати гарне ім’я класу важливо не лише тому, що він говорить комусь, що може зробити клас; але однаково назва класу також говорить про те, що клас не повинен робити.
Данк

3

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

Я гадаю, ти можеш використовувати Database, як ComponentDatabase. Або ви можете просто використовувати Components, Systemsі Entities(це я особисто використовую, і головним чином лише тому, що мені подобаються стислі ідентифікатори, хоча він, мабуть, передає ще менше інформації, можливо, ніж "Менеджер").

Одна частина, яку я вважаю трохи незграбною, це така:

Entity entity = scene.getEntityManager().getPool().create();

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

Але НЕ знаю, я бачив деякі дуже уяви і загальні види використання Manager, Handler, Adapterі назви такого роду, але я думаю , що це розумно можна пробачити використовувати випадок , коли річ під назвою «Менеджер» фактично відповідає за «управління» ( "контроль? "," поводження? ") життя предметів, відповідальність за створення та знищення та надання доступу до них окремо. Принаймні, для мене це найпростіше та інтуїтивно зрозуміле використання "Менеджера".

Однак, одна із загальних стратегій, щоб уникнути "Менеджера" від вашого імені, - просто вийняти частину "Менеджер" і додати додаток -sв кінці. :-D Наприклад, скажімо, що у вас є ThreadManagerі він відповідає за створення потоків та надання інформації про них, доступ до них тощо, але він не об'єднує потоки, тому ми не можемо його назвати ThreadPool. Ну, у такому випадку ви просто це називаєте Threads. Це я все одно роблю, коли не уявляю.

Мені якось нагадали, коли аналогічний еквівалент "Провідника Windows" називався "Файловий менеджер", наприклад:

введіть тут опис зображення

І я дійсно думаю, що "Файловий менеджер" у цьому випадку є більш описовим, ніж "Провідник Windows", навіть якщо там є краща назва, яка не передбачає "Менеджер". Тому, принаймні, будь ласка, не надто захоплюйтесь своїми іменами і починайте називати такі речі, як "ComponentExplorer". "ComponentManager" принаймні дає мені розумне уявлення про те, що робить ця річ, як хтось звик працювати з двигунами ECS.


Домовились. Якщо клас використовується для типових управлінських обов'язків (створення ("найм"), знищення ("звільнення"), нагляд та інтерфейс "працівник" / "високогіпер"), то ім'я Managerвідповідне. Клас може мати проблеми з дизайном , але ім'я знаходиться на місці.
Джастін Час 2 Відновіть Моніку
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.