Есть базовый класс A. Есть наследник В. И есть наследник наследника С.
Есть некая структура данный. Она парсится из файла.
Если в этой структуре все поля после парсинга заполнились — то на основе этой структуры создается класс С.
Если каких-то полей нет, но чуть больше чем совсем минимум — то создается класс В
А если совсем только базовый минимум полей заполнились из файла, то создается объекта класса А.
Ясно, что тупым «if» проверяя поля структуры можно проверять какой именно класс создавать. Но что-то некрасиво как-то. Можно как-то стильно модно молодежно, С++-но и по последним стандартам с сотней шаблонов и SFINAE разрулить создание нужного класса на основе прочитанной структуры?
Здравствуйте, Hоmunculus, Вы писали:
H>Ясно, что тупым «if» проверяя поля структуры можно проверять какой именно класс создавать. Но что-то некрасиво как-то. Можно как-то стильно модно молодежно, С++-но и по последним стандартам с сотней шаблонов и SFINAE разрулить создание нужного класса на основе прочитанной структуры?
Нельзя такие if-ы.
Если потребность в таких ифах в конструкторе возникает, что это симптом плохого дизайна и нарушение С++ стайла.
Так пишут на СИ.
Здравствуйте, imh0, Вы писали:
I>Здравствуйте, Hоmunculus, Вы писали:
H>>Ясно, что тупым «if» проверяя поля структуры можно проверять какой именно класс создавать. Но что-то некрасиво как-то. Можно как-то стильно модно молодежно, С++-но и по последним стандартам с сотней шаблонов и SFINAE разрулить создание нужного класса на основе прочитанной структуры?
I>Нельзя такие if-ы. I>Если потребность в таких ифах в конструкторе возникает, что это симптом плохого дизайна и нарушение С++ стайла. I>Так пишут на СИ.
Здравствуйте, Hоmunculus, Вы писали:
H>А ты вопрос-то мой прочитал ли?
Не... )
Помоему какой-то патерн разпознался. Сам уже перечитал и понял что у тебя совсем про другое.
Ну тут скорее нужен парсер в обьект "результат парсинга" и потом уже какой-то конвертор.
Но вообще тут точно нужен какой-то правльный дизайн наследования.)
Здравствуйте, Hоmunculus, Вы писали:
H>Есть базовый класс A. Есть наследник В. И есть наследник наследника С. H>Есть некая структура данный. Она парсится из файла. H>Если в этой структуре все поля после парсинга заполнились — то на основе этой структуры создается класс С. H>Если каких-то полей нет, но чуть больше чем совсем минимум — то создается класс В H>А если совсем только базовый минимум полей заполнились из файла, то создается объекта класса А.
Если это твой внутренний утилитарный формат, который сам формируешь и сохраняешь (сериализуешь) объекты тех же типов, которые затем читаешь, то самое простое указывать для сохраняемых данных тип к которому они принадлежат (type discriminator) — строка, или числовой идентификатор. Соотв. при чтении (десериализации) на основании этого идентификатора создаёшь нужный тип A/B/C.
Здравствуйте, Hоmunculus, Вы писали:
H>Здравствуйте, pilgrim_, Вы писали:
_>>Соотв. при чтении (десериализации) на основании этого идентификатора создаёшь нужный тип A/B/C.
H>И в этом моменте будет «if»
Это у же другой "if" , там может быть и switch, и словарь, в котором каждому идентифкатору типа будет соотв. своя фабрика его создания, предварительно в этом словаре могут быть зарегистрированны поддерживаемые типы.
Здравствуйте, Hоmunculus, Вы писали:
H>Нет. Некрасиво. Лишняя сущность без необходимости.
Т.к. конкретный тип (его идентификатор), которому соотв. данные, известен только в динамике (читаешь из файла), то в любом случае будет if/switch/фабрика для его создания
H>Необходимо именно как-то уже прочитанную структуру обернуть соответствующим ее заполнению классом.
Дык это уже другая задача — инициализация конкретного объекта конкретного типа (созданного на предыдущем шаге), тут может помочь виртуальная инициализацияя, псевдокод:
struct A {
virtual void initialize(const твоя_прочитанная_структура& data) {
//тут A знает какие поля брать из data
}
}
struct B : A {
void initialize(const твоя_прочитанная_структура& data) override {
A::initialize(data);
//тут B знает какие поля брать из data
}
}
struct C : B {
void initialize(const твоя_прочитанная_структура& data) override {
B::initialize(data);
//тут C знает какие поля брать из data
}
}
...
A* create_instance(const твоя_прочитанная_структура& data) {
A* obj = ...;
return obj;
}
...
твоя_прочитанная_структура data = read_data(...);
A* obj = create_instance(data);
obj->initialize(data);
H>Это сделать элементарно. Задача — без проверок. Перевод всех проверок на сравнение иднетиыткатора — ничего не меняет
Здравствуйте, Hоmunculus, Вы писали:
H>Есть базовый класс A. Есть наследник В. И есть наследник наследника С. H>Есть некая структура данный. Она парсится из файла. H>Если в этой структуре все поля после парсинга заполнились — то на основе этой структуры создается класс С. H>Если каких-то полей нет, но чуть больше чем совсем минимум — то создается класс В H>А если совсем только базовый минимум полей заполнились из файла, то создается объекта класса А.
H>Ясно, что тупым «if» проверяя поля структуры можно проверять какой именно класс создавать. Но что-то некрасиво как-то. Можно как-то стильно модно молодежно, С++-но и по последним стандартам с сотней шаблонов и SFINAE разрулить создание нужного класса на основе прочитанной структуры?
Сам по себе IF в коде не является злом. Особенно если IFы сидят внутри (инкапсулированы, спрятаны от клиентского кода).
А вот если IFами обвешана бизнес-логика (т.н. "клиентский код"), это уже антипаттерн.
По GoF правильным считается архитектура, когда клиентский код использует тип A (за которым в рантайме, благодаря ООП, может прятаться любой его подкласс — B или С). Для создания экземпляра типа A клиентский код вызывает парсер, который возвращает тип A:
parser {
A parse(filename) {}
}
Внутри парсера инкапсулирована логика, которая парсит файл и вызывает GoF-паттерн "фабрика" (сигнатура вызова тоже возвращает тип A, например: A createByparams(params)), передавая ей распарсенные параметры.
Внутри фабрики инкапсулирована логика, которая в зависимости от полноты параметров, с помощью IF, инстанцирует экземпляр A, B или С. В коде фабрики IF не запрещены, т.к. это не бизнес-логика, а вспомогательная логика.
В чём профит такого подхода: в коде бизнес-логики мы соблюдаем принципы SOLID, т.к. бизнес-логика оперирует только типом А. Код бизнес-логики становится простым (не обременённым IFами), расширяемым (для добавления нового подтипа А ты просто добавляешь новый подкласс, остальной код, особенно код бизнес-логики, ты не трогаешь, тем самым минимизируя вероятность регрессионных багов) и более надёжным, чем "матрёшка из IFов".
Но и правильно спроектировать такой код сложнее. Например, тебе нужно соблюсти принцип подстановки Лисковы из SOLID, т.е. чтобы бизнес-код оперировал А, даже если в рантайме за ним прячется B или С, и клиент не должен анализировать реальный подтип А, т.е. в бизнес-коде запрещены такие конструкции: if (a instanceof C) then...
Это иногда бывает не так просто.
Не понял где в твоем коде какой именно класс создается?
То что initialize виртуальная — это понятно. И что на базе структуры можно все три класса создать — тоже понятно. Как узнать максимально возможный класс без if?
Здравствуйте, Hоmunculus, Вы писали:
H>Не понял где в твоем коде какой именно класс создается? H>То что initialize виртуальная — это понятно. И что на базе структуры можно все три класса создать — тоже понятно. Как узнать максимально возможный класс без if?
Например, попытаться в цикле создать объект. От С к А.
unique_ptr<A> ctorA()
{
return make_unique<A>();
}
unique_ptr<A> ctorB()
{
return make_unique<B>();
}
unique_ptr<A> ctorC()
{
return make_unique<C>();
}
unique_ptr<A> create_instance(const твоя_прочитанная_структура& data)
{
for(auto ctor : {ctorC, ctorB, ctorA})
{
unique_ptr<A> obj = ctor(); // создал экземпляр нужного классаif (obj->initialize(data)) // если он сумел себя полностью инициализироватьreturn obj; // отдаём наружу
// иначе переходим к классу "попроще".
}
return {}; // Вообще никого не удалось сконструировать - фигня какая-то зачиталась
}
_____________________
С уважением,
Stanislav V. Zudin
Здравствуйте, Hоmunculus, Вы писали:
H>Здравствуйте, pilgrim_, Вы писали:
H>Не понял где в твоем коде какой именно класс создается?
Вот тут на основании данных в data определяется, какой тип нужно создать:
_>A* create_instance(const твоя_прочитанная_структура& data) { _> A* obj = ...; _> return obj; _>}
более детально, пример, псевдокод:
enum eTypeId {
a, b, c
};
std::map<eTypeId, std::function<A*()>> factory;
void init_factory() {
factory[eTypeId::a] = [] () { return new A(); };
factory[eTypeId::b] = [] () { return new B(); };
factory[eTypeId::c] = [] () { return new C(); };
}
A* create_instance(const my_data& data) {
//тут по хорошему надо поверить, что есть фабрика для данного типа
A* obj = factory[data.typeId]();
return obj;
}
...
init_factory();
...
твоя_прочитанная_структура data = read_data(...);
A* obj = create_instance(data);
obj->initialize(data);
H>То что initialize виртуальная — это понятно. И что на базе структуры можно все три класса создать — тоже понятно. Как узнать максимально возможный класс без if?
В динамике никак.
Если идея с идентификатором типа не нравится (не хочешь/можещь менять существующий формат данных), то можно определить набор требуемых полей для каждого типа (они уже есть, судя по изначальному посту), на основе их наличия сформировать свой динамический идентификатор типа (напр. в виде строки), а далее см. выше (factory, create_instance), ну без сравнений всё-равно не обойтись.
SVZ>> if (obj->initialize(data)) // если он сумел себя полностью инициализировать
SVZ>> return obj; // отдаём наружу
SVZ>>
_>тут ты и попался
а я, как будто, из последних сил
unique_ptr<A> create_instance(const твоя_прочитанная_структура& data)
{
for(auto ctor : {ctorC, ctorB, ctorA})
{
try
{
unique_ptr<A> obj = ctor(); // создал экземпляр нужного класса
obj->initialize(data); // если он сумел себя полностью инициализироватьreturn obj; // отдаём наружу
}
catch(IncompleteData&)
{
// иначе переходим к классу "попроще".
}
catch(...)
{
throw;
}
}
return {}; // Вообще никого не удалось сконструировать - фигня какая-то зачиталась
}
Где ты видишь if'ы?
_____________________
С уважением,
Stanislav V. Zudin
SVZ> unique_ptr<A> obj = ctor(); // создал экземпляр нужного класса SVZ> if (obj->initialize(data)) // если он сумел себя полностью инициализировать SVZ> return obj; // отдаём наружу
Я бы тело цикла заменил на
if (unique_ptr<A> obj = ctor(data); obj)
return obj;
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
SVZ>> unique_ptr<A> obj = ctor(); // создал экземпляр нужного класса SVZ>> if (obj->initialize(data)) // если он сумел себя полностью инициализировать SVZ>> return obj; // отдаём наружу
TB>Я бы тело цикла заменил на TB>
Здравствуйте, Hоmunculus, Вы писали:
H>Есть базовый класс A. Есть наследник В. И есть наследник наследника С. H>Есть некая структура данный. Она парсится из файла. H>Если в этой структуре все поля после парсинга заполнились — то на основе этой структуры создается класс С. H>Если каких-то полей нет, но чуть больше чем совсем минимум — то создается класс В H>А если совсем только базовый минимум полей заполнились из файла, то создается объекта класса А.
Разве это не Factory?
Там в любом случае будут ветвления на этапе создания.
Здравствуйте, Doom100500, Вы писали:
D>Разве это не Factory? D>Там в любом случае будут ветвления на этапе создания.
Ну по сути да, но так как случай не общий, а вполне конкретный и то, что у нас более расширенные данные должны порождать более расширенный класс — это ж как бы в сути наследования, думал мож какой отдельный специальный для такого механизм есть.
H>Ну по сути да, но так как случай не общий, а вполне конкретный и то, что у нас более расширенные данные должны порождать более расширенный класс — это ж как бы в сути наследования, думал мож какой отдельный специальный для такого механизм есть.
Эээ. Композиция? Правда кода больше писать придётся в самих классах. Возврат нужных инерфейсов, методы их запроса.
Здравствуйте, Hоmunculus, Вы писали:
H>Ясно, что тупым «if» проверяя поля структуры можно проверять какой именно класс создавать. Но что-то некрасиво как-то. Можно как-то стильно модно молодежно, С++-но и по последним стандартам с сотней шаблонов и SFINAE разрулить создание нужного класса на основе прочитанной структуры?
Т.е. вы хотите статический полиморфизм в динамике? Ну я даже не знаю...
Заведите мапу
размер -> функтор создания объекта
используйте.
Пожалей людей, которые будут потом это поддерживать.
Ты хочешь, имея простое и понятное решение, заменить его за громоздкое, непонятное => более дорогое в разработке и поддержке. И не имеющее никакого преимущества в райнтайме
Бизнес такое не одобряет