Шаблон проектирования: единый интерфейс для частично поддерживаемых производных классов - PullRequest
0 голосов
/ 20 ноября 2010

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

Например, (мой текущий код)

Class Base { //this is the main interface
public:
   virtual void basicFun() = 0;
   virtual void funA(int input1) {return;}
   virtual void funB(int input2) {return;}
   virtual void funC(int input3) {return;}
}

Class Derived1 : public Base { //this class support only funA()
public:
   void basicFun() {//....}
   void funA(int input1) {//do something}
}

Class Derived2 : public Base { //this class support both funA() funB()
public:
   void basicFun() {//....}
   void funA(int input1) {//do something}
   void funB(int input2) {//do something}
}

Class Derived3 : public Base { //this class support all
public:
   void basicFun() {//....}
   void funA(int input1) {//do something}
   void funB(int input2) {//do something}
   void funC(int input3) {//do something}
}

Предположение: для определенного объекта неподдерживаемая функция никогда не будет вызываться. Т.е. BasePtr-> funC () никогда не будет вызываться, если объект, на который указывает basePtr, равен Derived1 или Derived2. Проблема:

  1. Я должен определить пустую функцию в Base или Derived, если требуется унифицированный интерфейс
  2. Если пустые функции определены, как указано выше, компилятор предупреждает меня о параметрах без ссылок (input1 ~ input3). Конечно, я могу выключить его, но мне просто не нравится.

Итак, есть ли шаблон, который я могу использовать для достижения единого интерфейса без определения пустых функций? Я думал об этом в течение нескольких дней. Это кажется невозможным. Потому что funA () funB () и funC () должны быть в интерфейсе, чтобы я мог использовать массив указателей Base для управления всеми объектами, что означает, что в Derived1, funB () и funC () должны быть как-то определены.

Спасибо, счастливого дня благодарения и спасибо за то, что поделились своими идеями.

Solti

Ответы [ 3 ]

1 голос
/ 20 ноября 2010

Единые интерфейсы - это хорошо.Это означает, что вы должны реализовать все методы в интерфейсе, даже если это означает, что у вас будут пустые методы.Для этой проблемы нет шаблона проектирования, потому что, во-первых, это не настоящая проблема.

Подумайте об этом на мгновение: скажем, у вас есть интерфейс Car с методами Accelerate() и Brake().Класс, производный от Car, должен реализовывать все методы.Хотели бы вы, чтобы объект, полученный из Car, реализовал метод Accelerate(), но не метод Brake()?Это было бы удивительно небезопасно Car!

Интерфейс в контексте ООП должен иметь четко определенный контракт, которого придерживаются обе стороны.В C ++ это обеспечивается в определенной степени, требуя, чтобы все чистые виртуалы были реализованы в производном классе (ах).Попытка создания экземпляра класса с нереализованными виртуальными методами приводит к ошибке (ошибкам) ​​компиляции, если предположить, что никто не использует глупые уловки, чтобы обойти это.

Вы возражаете против создания пустых методов, потому что они вызывают предупреждения компилятора.В вашем случае просто опустите имя параметра:

void funC(int) // Note lack of parameter name
{
}

Или закомментируйте имя:

void funC(int /*input3*/)
{
}

Или даже с помощью шаблонов!

template<class T> void ignore( const T& ) { }

//...

void funC(int input3)
{
    ignore(input3);
}
0 голосов
/ 27 ноября 2010

Если вам действительно нужны неоднородные интерфейсы (подумайте об этом раньше), возможно, шаблон Visitor стоит попробовать:

struct Visitor;

struct Base
{
    virtual ~Base() {}
    virtual void accept(Visitor& v) { v.visit(*this); }
};

struct InterfaceA : Base
{
    void accept(Visitor& v) { v.visit(*this); }
    virtual void MethodA() = 0;
};

struct InterfaceB : Base
{
    void accept(Visitor& v) { v.visit(*this); }
    virtual void MethodB() = 0;
};

struct InterfaceA2 : InterfaceA
{
    void accept(Visitor& v) { v.visit(*this); }
    void MethodA(); // Override, eg. in terms of MethodC
    virtual void MethodC() = 0;
};

// Provide sensible default behavior. Note that the visitor class must be
// aware of the whole hierarchy of interfaces
struct Visitor
{
    virtual ~Visitor() {}
    virtual void visit(Base& b) { throw "not implemented"; }
    virtual void visit(InterfaceA& x) { this->visit(static_cast<Base&>(x)); }
    virtual void visit(InterfaceA2& x) { this->visit(static_cast<InterfaceA&>(x)); }
    virtual void visit(InterfaceB& x) { this->visit(static_cast<Base&>(x)); }
};


// Concrete visitor: you don't have to override all the functions. The unimplemented
// ones will default to what you expect.
struct MyAction : Visitor
{
    void visit(InterfaceA& x)
    {
        x.MethodA();
    }

    void visit(InterfaceB& x)
    {
        x.methodB();
    }
};

использование:

Base* x = getSomeConcreteObject();
MyAction f;
x->accept(f);

это вызовет либоMethodA или MethodB в зависимости от типа времени выполнения x.Реализованный посетитель позволяет не перегружать многие функции и возвращается к действию для базового класса, если поведение не реализовано.В конце концов, если вам не удастся выполнить действие в посетителе для какого-либо класса, по умолчанию оно будет throw "not implemented".

Правильность Const может заставить вас различать Visitor и ConstVisitor, для которых всеaccept методы будут постоянными.

0 голосов
/ 27 ноября 2010

Вот что я бы сделал: создаю вспомогательный базовый класс с пустыми реализациями по умолчанию всех чисто виртуальных методов. Затем вы можете наследовать этот базовый класс вместо основного интерфейса и затем выбрать метод для переопределения.

// main interface, everything pure virtual
struct IBase
{
   virtual ~IBase() {}
   virtual void basicFun() = 0;
   virtual void funA(int input1) = 0;
   virtual void funB(int input2) = 0;
   virtual void funC(int input3) = 0;
};

// helper base class with default implementations (empty)
class Base : public IBase
{
   void basicFun() {}
   void funA(int input1) {}
   void funB(int input2) {}
   void funC(int input3) {}
};

class Derived1 : public Base { //this class support only funA()
   void funA(int input1) {//do something}
};

class Derived2 : public Base { //this class support both funA() funB()
   void funA(int input1) {//do something}
   void funB(int input2) {//do something}
};

class Derived3 : public IBase { //this class support all
   void basicFun() {//....}
   void funA(int input1) {//do something}
   void funB(int input2) {//do something}
   void funC(int input3) {//do something}
};

int main()
{
   // I always program to the interface
   IBase& b1 = Derived1(); b1.basicFun(); b1.funA(); b1.funB(); b1.funC();
   IBase& b2 = Derived2(); b2.basicFun(); b2.funA(); b2.funB(); b2.funC();
   IBase& b3 = Derived3(); b3.basicFun(); b3.funA(); b3.funB(); b3.funC();
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...