Здравствуйте, Максим Рогожин, Вы писали:
МР>Допустим есть коллекция: МР>
std::vector<IElement> coll;
МР>
Допустим, эта коллекция пытается содержать значения абстрактного типа, за что компилятор надаёт по пальцам.
Коллекция полиморфных объектов должна хранить их по указателям — иначе будет или срезка (если тип воплощаемый), или ошибка компиляции (если он абстрактный).
Вот хотя бы как-то так
std::vector<std::shared_ptr<IElement>> coll;
МР>и надо обработать каждый элемент. Как это сделать? С помощью паттерна Визитор? МР>
МР>Но тип *this будет ConcreteFoo, а надо IFoo, так как все должно работать через интерфейсы, а не через конкретные типы. Подскажите, пожалуйста.
Ну и чего плохого в том, что посетителю будет передан указатель на ConcreteFoo, который является субклассом IFoo?
Если посетитель умеет отличать только IFoo от IElement, так и слава богу. А если он ещё и с ConcreteFoo особым образом работает — так вообще всё здорово.
struct Visitor { // это интерфейс, кстати говоря!virtual void visit(IElement*) = 0;
virtual void visit(IFoo*) = 0;
// добавь сюда все известные тебе типы элементов
// напримерvirtual void visit(ConcreteFoo*) = 0;
};
А если нужно, чтобы ConcreteFoo не протёк в посетителя, — определи accept у интерфейсов, а конкретный класс не трогай.
struct IElement {
...
virtual void accept(Visitor* v) { v->accept(this); } // в базовом интерфейсе объявление в любом случае потребуется
...
};
struct IFoo : IElement {
...
void accept(Visitor* v) override { v->accept(this); }
...
};
А вообще, все эти недо-мультиметоды по рецептам Банды Четырёх — как-то навевают уныние.
Прежде чем возиться с реализацией, надо сперва чётко обосновать, что и зачем делается. Возможно, что надо делать совсем другое и — потому — совсем иначе. Прежде чем бросаться на амбразуру и рожать двойную диспетчеризацию для всей иерархии классов.
Здравствуйте, Максим Рогожин, Вы писали:
МР>Привет!
МР>Хочу написать приложение, в котором все основные сущности будут представлены своими интерфейсами. Конкретные реализации будут создаваться в фабриках.
МР>...
Здравствуйте, Кодт, Вы писали:
К>Вот хотя бы как-то так К>
К>std::vector<std::shared_ptr<IElement>> coll;
К>
Да, это я и имел ввиду.
К>А если он ещё и с ConcreteFoo особым образом работает — так вообще всё здорово.
ConcreteFoo вообще не должен использоваться нигде кроме как в фабрике — идея в том, чтобы все приложение было написано в терминах интерфесов.
К>
К>struct Visitor { // это интерфейс, кстати говоря!
К> virtual void visit(IElement*) = 0;
К> virtual void visit(IFoo*) = 0;
К> // добавь сюда все известные тебе типы элементов
К> // например
К> virtual void visit(ConcreteFoo*) = 0;
К>};
К>
А передавать в метод visit наверное лучше тоже shared_ptr<IFoo>?
К>А если нужно, чтобы ConcreteFoo не протёк в посетителя, — определи accept у интерфейсов, а конкретный класс не трогай. К>[c] К>struct IElement { К> ... К> virtual void accept(Visitor* v) { v->accept(this); } // в базовом интерфейсе объявление в любом случае потребуется К> ... К>};
Почему у интерфесов? У интерфесов же недолжны методы быть определены. Я сделаю в интерфейсе объявление метода accept:
struct IElement {
virtual void accept(Visitor& v) =0;
};
struct IFoo : public IElement {
// унаследовал объявление метода accept от IElement
};
class ConcreteFoo : public IFoo {
public:
void accept(Visitor &v) override { // конкретные классы будут реализовывать методы accept
v.visit(*this); // вызовется virtual visit(IFoo*)
}
};
К>Прежде чем возиться с реализацией, надо сперва чётко обосновать, что и зачем делается.
Практикуется разработка дизайна, основанного на интерфейсах. И как работать с иерархией интерфейсов без dynamic_cast-ов.
Здравствуйте, Максим Рогожин, Вы писали:
К>>А если нужно, чтобы ConcreteFoo не протёк в посетителя, — определи accept у интерфейсов, а конкретный класс не трогай. МР>Почему у интерфесов? У интерфесов же недолжны методы быть определены. Я сделаю в интерфейсе объявление метода accept:
Дело вкуса. Поскольку интерфейс посетителя (I)Visitor перегрузил свой accept(IFoo*), accept(ISomethingElse*),.. то мы из ConcreteFoo попадём в нужную сигнатуру — а дальше всё как с мультиметодами на двойной диспетчеризации.
Но если таких ConcreteFoo1, ConcreteFoo2,... много, то было бы очень естественно сделать абстрактный базовый класс, реализующий visit(IVisitor*) такой, что он приводится к IVisitor::accept(IFoo*).
Можно разнести чистый интерфейс и надстройку (тогда у интерфейса смело можно делать novtbl), а можно совместить.
К>>Прежде чем возиться с реализацией, надо сперва чётко обосновать, что и зачем делается. МР>Практикуется разработка дизайна, основанного на интерфейсах. И как работать с иерархией интерфейсов без dynamic_cast-ов.
Я к тому, что — а вот зачем с самого начала делать универсального посетителя?
Это заявка на то, что мы не знаем, ни что за объекты лежат в коллекции, ни что за действия над ними будем совершать, — но с этой заведомой абстракцией мы расплачиваемся тем, что фиксируем набор интерфейсов, во-первых (ведь их все надо перечислить как сигнатуры функции accept у интерфейса IVisitor), и довольно узким горлышком для операций (теперь, чтобы параметризовать действие над объектами, нам надо будет все связанные переменные добавить как члены объекта-экземпляра IVisitor; и более того, придётся на каждую операцию создавать новый тип-наследник IVisitor).
Вот этот всемогутор, связанный по рукам и ногам, как-то и напрягает.
Если забег по коллекции объектов сводится к выполнению одинаковых действий над ними всеми (с точностью до переопределений в наследниках), то посетитель не нужен; достаточно, чтобы в интерфейсе элементов коллекции были нужные методы для вызова.
Если забег сводится к фильтрации — над объектами типа IFoo действия выполняем, над другими не выполняем, то посетитель будет играть роль рукодельного dynamic_cast, на самом-то деле!
Почему бы тогда не реализовать этот кастинг с самого начала? В интерфейс IElement понапихать кастинг-методы get_as_IFoo, get_as_IBar, если dynamic_cast чем-то не нравится. И получится тот же прибитый гвоздями список типов, только не в IVisitor, а прямо в IElement.
(А потом собрать отфильтрованную коллекцию уже известного подтипа — и см.выше про одинаковые действия).
Коллекция, наполненная слишком разнородными объектами — выглядит как помойка, свидетельствующая о чём-то плохом в дизайне.
Например, о том, что "мы не вправе решать за пользователя, какой треш он нам пришлёт, но мы не должны при этом упасть и должны удовлетворить клиента".
Вот все эти явовские object[], — это от безысходности или от абстрактной паранойи (не хотим отдавать наружу наши интерфейсы, пусть ответная сторона думает, что мы — чёрный ящик).
В общем, техническую сторону, как посетителя реализовать, — я надеюсь, понятно. А мотивационную — на усмотрение.
Здравствуйте, Максим Рогожин, Вы писали:
МР>А передавать в метод visit наверное лучше тоже shared_ptr<IFoo>?
Тогда придётся делать enable_shared_from_this.
Или всё сделать на интрузивных умных указателях.
Или на голых — и тогда следить за временем жизни элементов коллекции.