Возможно ли иметь шаблонный класс c ++ для общей реализации интерфейса T и переадресации всех вызовов в обернутый объект, реализующий T? - PullRequest
0 голосов
/ 13 марта 2020

Возможно ли иметь шаблонный класс c ++, который в общем случае реализует интерфейс T и динамически делегирует вызовы одному или нескольким обернутым объектам, которые реализуют T?

Как обычно, лучше всего использовать код, чтобы объяснить, что я пытаюсь сделать:

// There are many interfaces:
class Foo {
 public:
  virtual ... DoFoo(...);
  ...
}
class Bar {
 public:
  virtual ... DoBar(...);
  ...
}
class InterfaceN { ... }
...

// Each interface has multiple implementations:
class SimpleFoo : public Foo { ... }
class FancyFoo : public Foo { ... }
class SimpleBar : public Bar { ... }
class FancyBar : public Bar { ... }
class SimpleInterfaceN : public InterfaceN { ... }
class FancyInterfaceN : public InterfaceN { ... }
...

// There are many consumers of these interfaces:
class ConsumerA {
 public:
  ConsumerA(Foo foo, Bar bar, ...);
 ...
}
class ConsumerB { ... }  // uses Foo, Interface2, Interface5
class ConsumerC { ... }  // uses Bar, Interface2
...

В настоящее время все потребители используют простые реализации. Мне нужно иметь возможность динамически переключаться между простыми и причудливыми реализациями без изменения потребителей и существующих простых реализаций. Неважно, что это за механизм, который меняет реализации. Я мог бы изменить все причудливые реализации так, чтобы они воспринимали простой объект реализации, а затем динамически переключаться между использованием собственной причудливой реализации или делегировать простой реализации в каждом вызове метода интерфейса (т. Е. Включать переключение логики c в реализации).

// For each interface, add switching logic to fancy implementation:
class FancyFoo : public Foo {
 public:
  FancyFoo(SimpleFoo* simple_foo, ...) override;
  ... DoFoo(...) override {
    if (use_fancy) {
      // fancy implementation
      ...
    } else {
      return simple_foo_->DoFoo(...);
    }
  }
  ...
}

В идеале я предпочитаю не загрязнять реализации несвязанными логиками переключения c, поэтому вместо этого я могу сделать третью реализацию, которая выполняет переключение:

class FooSwitcher : public Foo {
 public:
  FooSwitcher(Foo* first_foo, Foo* second_foo) {
    active_foo_ = first_foo;
    inactive_foo_ = second_foo;
  }
  ... DoFoo(...) {
    return active_foo_->DoFoo(...);
  }
  void SwitchFoos() {
    swap(active_foo_, inactive_foo_);
  }
  ...
 private:
  Foo* active_foo_;
  Foo* inactive_foo_;
  ...
}

Итак, наконец, мой вопрос это ... Возможно ли иметь обобщенный класс c switcher, чтобы лог переключения c записывался один раз и мог быть повторно использован для каждого интерфейса, который в этом нуждается? Примерно так:

template <typename T>
class Switcher : public T {
 public:
  Switcher(T first_impl, T second_impl) { ... }
  ***magic that forwards/delegates all calls to any T methods to active_impl_***
 private:
  T active_impl_;
  T inactive_impl_;
}

... и будет использоваться что-то вроде этого:

ConsumerA consumer_a = new ConsumerA(
    Switcher<Foo>(simple_foo, fancy_foo),
    Switcher<Bar>(simple_bar, fancy_bar),
    ...);

1 Ответ

0 голосов
/ 13 марта 2020

Это простой (без учета дополнительных факторов) шаблон класса Switcher, который может быть тем, что вы ищете:

template<typename first, typename second, typename base>
class Switcher{
public:
    Switcher(first& _first, second& _second):first_object(&_first), second_object(&_second), active(first_object){}
    void switch_them(){
        if(active == first_object) active = second_object;
        else active = first_object;
    }
    base* operator &()const{
        return active;
    }
    base* operator ->()const{
        return active;
    }
private:
    first* const first_object;
    second* const second_object;
    base* active;
};

Обратите внимание, что, как и другие предлагали, хотя с использованием оператора адресации, operator&, здесь все в порядке, вы также можете использовать operator -> для достижения аналогичной функциональности. Я предлагаю выбрать только один из них, чтобы зарезервировать другой для будущего нормального использования.

В случае простого Foo и его простых производных:

class Foo{
 public:
    virtual int DoFoo() = 0;
    virtual ~Foo(){}
};

class SimpleFoo:public Foo{
 public:
    int DoFoo(){
        return 0;
    }
};

class FancyFoo:public Foo{
 public:
    int DoFoo(){
        return 1;
    }
};

Простое использование становится таким:

int main() {
    Foo* foo_pointer;
    SimpleFoo simplefoo;
    FancyFoo fancyfoo;
    Switcher<SimpleFoo, FancyFoo, Foo> switcher(simplefoo, fancyfoo);
    foo_pointer = &switcher;
    std::cout << foo_pointer->DoFoo() << std::endl;
    std::cout << switcher->DoFoo() << std::endl;
    switcher.switch_them();
    foo_pointer = &switcher;
    std::cout << foo_pointer->DoFoo() << std::endl;
    std::cout << switcher->DoFoo() << std::endl;
    switcher.switch_them();
    foo_pointer = &switcher;
    std::cout << foo_pointer->DoFoo() << std::endl;
    std::cout << switcher->DoFoo() << std::endl;

    return 0;
}

И, наконец, простой результат:

0
0
1
1
0
0

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

...