Как работать с производным интерфейсом?
От: Максим Рогожин Россия  
Дата: 27.05.18 11:09
Оценка:
Привет!

Хочу написать приложение, в котором все основные сущности будут представлены своими интерфейсами. Конкретные реализации будут создаваться в фабриках.

Например, имеется иерархия интерфейсов:
class IElement {
public:
   int getId() const =0; // общий метод
   virtual ~IElement() {};
};

class IFoo : public IElement {
public:
   int getSize() const =0; // специфический для IFoo метод
   virtual ~IFoo() {};
};

class IBar : public IElement {
public:
   bool isValid() const =0;  // специфический для IBar метод
   virtual ~IBar() {};
};


Допустим есть коллекция:
std::vector<IElement> coll;


и надо обработать каждый элемент. Как это сделать? С помощью паттерна Визитор?
class ConcreteFoo : public IFoo {
public:
  void accept(Visitor &v) override {
    v.visit(*this);
  }
};

Но тип *this будет ConcreteFoo, а надо IFoo, так как все должно работать через интерфейсы, а не через конкретные типы. Подскажите, пожалуйста.
Отредактировано 27.05.2018 11:15 Максим Рогожин . Предыдущая версия .
Re: Как работать с производным интерфейсом?
От: reversecode google
Дата: 27.05.18 11:46
Оценка:
я понимаю что вы нихрена сами за мое время наблюдения вас на форуме ничего не хотите делать
так давайте вас будем учить
http://bit.ly/2IT26gy

а если зайти в вики то там даже готовый пример
https://en.wikipedia.org/wiki/Visitor_pattern
Re: Как работать с производным интерфейсом?
От: Кодт Россия  
Дата: 28.05.18 12:13
Оценка: 2 (1) +5
Здравствуйте, Максим Рогожин, Вы писали:

МР>Допустим есть коллекция:

МР>
std::vector<IElement> coll;
МР>


Допустим, эта коллекция пытается содержать значения абстрактного типа, за что компилятор надаёт по пальцам.
Коллекция полиморфных объектов должна хранить их по указателям — иначе будет или срезка (если тип воплощаемый), или ошибка компиляции (если он абстрактный).

Вот хотя бы как-то так
std::vector<std::shared_ptr<IElement>> coll;


МР>и надо обработать каждый элемент. Как это сделать? С помощью паттерна Визитор?

МР>
МР>class ConcreteFoo : public IFoo {
МР>public:
МР>  void accept(Visitor &v) override {
МР>    v.visit(*this);
МР>  }
МР>};
МР>

МР>Но тип *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); }
  ...
};


А вообще, все эти недо-мультиметоды по рецептам Банды Четырёх — как-то навевают уныние.
Прежде чем возиться с реализацией, надо сперва чётко обосновать, что и зачем делается. Возможно, что надо делать совсем другое и — потому — совсем иначе. Прежде чем бросаться на амбразуру и рожать двойную диспетчеризацию для всей иерархии классов.
Перекуём баги на фичи!
Re: Как работать с производным интерфейсом?
От: SaZ  
Дата: 28.05.18 14:41
Оценка:
Здравствуйте, Максим Рогожин, Вы писали:

МР>Привет!


МР>Хочу написать приложение, в котором все основные сущности будут представлены своими интерфейсами. Конкретные реализации будут создаваться в фабриках.


МР>...


Можно попробовать boost::base_collection.
Re[2]: Как работать с производным интерфейсом?
От: Максим Рогожин Россия  
Дата: 28.05.18 17:32
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Вот хотя бы как-то так

К>
К>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-ов.
Re[3]: Как работать с производным интерфейсом?
От: Кодт Россия  
Дата: 28.05.18 18:57
Оценка: +1
Здравствуйте, Максим Рогожин, Вы писали:

К>>А если нужно, чтобы 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[], — это от безысходности или от абстрактной паранойи (не хотим отдавать наружу наши интерфейсы, пусть ответная сторона думает, что мы — чёрный ящик).

В общем, техническую сторону, как посетителя реализовать, — я надеюсь, понятно. А мотивационную — на усмотрение.
Перекуём баги на фичи!
Re[3]: Как работать с производным интерфейсом?
От: Кодт Россия  
Дата: 28.05.18 18:59
Оценка:
Здравствуйте, Максим Рогожин, Вы писали:

МР>А передавать в метод visit наверное лучше тоже shared_ptr<IFoo>?


Тогда придётся делать enable_shared_from_this.
Или всё сделать на интрузивных умных указателях.
Или на голых — и тогда следить за временем жизни элементов коллекции.

В общем, простор для работы!
Перекуём баги на фичи!
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.