Пастух

Эволюция менеджера для создания объектов (C++)

 

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

EpicFailObjectManager

С чего всё начинается когда молодой, зелёный программист пишет свой первый менеджер для управления объектами? В первую очередь надо применить все свои “знания” ООП и создать базовый объект, от которого в последствии унаследовать остальные. Итак, самый простой вариант:

Далее наследуем от него пачку объектов:

И пишем менеджер, который будет создавать эти самые объекты. Как понять какой тип объекта создать? Самая простая мысль это завести перечисление, каждое значение которого будет соответствовать конкретному объекту. Потом тупо switch case и вуаля:

Попробуем использовать новоиспеченный менеджер:

Подведём итог нашего, мягко сказать упоротого менеджера. Самая большая проблема, для того что бы добавить новый тип объекта надо: добавить его в перечисление, изменить функцию создания, что ведёт за собой перекомпиляцию менеджера, т.е. никакой модульности и расширяемости. В общем полный провал.

FailObjectManager

Следующая мысль которая меня посетила, надо избавиться от перечисления, перевести всё на строку и будет счастье. И вынести функцию создания объектов из самого менеджера. Тогда компилируем менеджер один раз, а функцию создания храним в конкретном проекте.

Ну вот, чисто в теории, мы получили менеджер, который может создавать любые объекты унаследованные от ObjectBase. Примерно на таком менеджере у нас вышло пара игр 🙂 Но проблем всё равно вагон и маленькая тележка. Во первых тот самый статический TYPE, который нужно по любому создавать для каждого объекта. Во вторых функция создания, на каждый новый проект её приходится писать заново. т.е. даже если какие-либо объекты уже устаканились и являются универсальными и не относятся, например к какой либо игре, их нужно постоянно учитывать в этой функции. Теперь самая главная задача избавиться от этой функции. Что бы мы могли просто создать новый header и cpp файл с реализацией объекта и всё. Не надо было куда то перекидывать указатели и пр.

ObjectManager

И в этом нам помогут шаблоны, тот самый тёмный лес языка cpp. Мы сделаем один шаблонный класс менеджера, который сможет создавать объекты абсолютно любого типа. Поехали.

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

Добавляем еще один внутренний шаблонный класс Register. Исходя из названия можно понять что служит класс именно для того, что бы заводить новые функции создания (Create). Функция InitCreator это то, что нам нужно вызвать для добавления нового типа в менеджер объектов. Сама регистрация нового типа это достаточно большая и не читаемая строчка на шаблонах и что бы упростить её делаем макрос принимающий три значения 1. Экземпляр менеджера.  2. Тип регистрируемого объекта. 3. Строка-ключ, обычно строковое представление типа.

Пример использования.

Объявляем базовый класс для объектов. И сразу объявляем тип для менеджера, который способен создавать объекты унаследованные от данного класса.

И делаем для примера два класса унаследованных от ObjectBase. Для каждого класса после его объявления вызываем макрос регистрации в менеджере объектов.

Ну и теперь пробуем использовать

На экране должно появиться две надписи:

Вот собственно и всё. В итоге получили легко расширяемую фабрику по созданию объектов. Может она еще не идеальна, но это пока лучшее найденное решение.