2013-05-28 3 views
9

Отказ от ответственности: я не смог четко описать, что именно я пытаюсь сделать, поэтому я надеюсь, что пример будет более ясным, чем мое объяснение! Пожалуйста, предложите любую повторную фразу, чтобы сделать ее более ясной. :)Методы перегрузки для реализации особых случаев интерфейса


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

Пример

#include <iostream> 

class BaseNode {}; 
class DerivedNode : public BaseNode {}; 

class NodeProcessingInterface 
{ 
public: 
    virtual void processNode(BaseNode* node) = 0; 
}; 

class MyNodeProcessor : public NodeProcessingInterface 
{ 
public: 
    virtual void processNode(BaseNode* node) 
    { 
     std::cout << "Processing a node." << std::endl; 
    } 

    virtual void processNode(DerivedNode* node) 
    { 
     std::cout << "Special processing for a DerivedNode." << std::endl; 
    } 
}; 

int main() 
{ 
    BaseNode* bn = new BaseNode(); 
    DerivedNode* dn = new DerivedNode(); 

    NodeProcessingInterface* processor = new MyNodeProcessor(); 
    // Calls MyNodeProcessor::processNode(BaseNode) as expected. 
    processor->processNode(bn); 
    // Calls MyNodeProcessor::processNode(BaseNode). 
    // I would like this to call MyNodeProcessor::processNode(DerivedNode). 
    processor->processNode(dn); 

    delete bn; 
    delete dn; 
    delete processor; 

    return 0; 
} 

Моя мотивация

Я хочу, чтобы иметь возможность реализовать несколько различных бетонных NodeProcessor s некоторые из которых будут относиться ко всем узлам одинаковы (т.е. реализовать только то, что показано в интерфейсе) и некоторые из них будут различать разные типы узлов (как в MyNodeProcessor). Поэтому я хотел бы, чтобы второй вызов processNode(dn) использовал реализацию в MyNodeProcessor::processNode(DerivedNode) путем перегрузки (некоторые части/подклассы) методов интерфейса. Это возможно?

Очевидно, что если я изменю processor на тип MyNodeProcessor*, тогда это работает так, как ожидалось, но мне нужно иметь возможность использовать разные процессоры узлов взаимозаменяемо.

Я также могу обойти это, имея один метод processNode(BaseNode), который проверяет точный тип его аргумента во время выполнения и ветви на основе этого. Мне кажется нецелесообразным включать эту проверку в мой код (особенно, когда число типов узлов растет, и у меня есть гигантский оператор switch). Я чувствую, что язык должен быть в состоянии помочь.


Я использую C++, но я заинтересован в общих ответов, а если вы предпочитаете (или, если это проще/отличается на других языках).

+2

Вы посмотрели шаблон посетителя? –

+2

Это называется проблемой «двойной отправки» http://stackoverflow.com/questions/tagged/double-dispatch –

+0

На самом деле, мое приложение использует шаблон посетителя, где я бы хотел, чтобы разные посетители посещали узлы с разными уровнями зернистость. То есть, один посетитель будет обрабатывать все узлы одинаково, в то время как другой будет обрабатывать все узлы по-разному, а другой может рассматривать некоторые типы узлов как общие, но включать специальную обработку некоторых других типов узлов. Я удалил все ссылки на это при создании моего MWE, поскольку это все еще кажется ядром проблемы. – Ben

ответ

2

Нет, это невозможно. Отправка виртуального метода происходит в compiletime, то есть использует статический тип указателя процессора, а именно NodeProcessingInterface. Если этот базовый тип имеет только одну виртуальную функцию, будет вызвана только одна виртуальная функция (или ее переопределяющие реализации). Компилятор не имеет возможности определить, что mig mig является производным классом NodeProcessor, реализующим более выдающиеся функции.

Таким образом, вместо того, чтобы разнообразить методы в производных классах, вы должны сделать это наоборот: Объявить все различные виртуальные функции, которые вам нужны в базовом классе переопределить их по мере необходимости:

class NodeProcessingInterface 
{ 
public: 
    virtual void processNode(BaseNode* node) = 0; 

    //simplify the method definition for complex node hierarchies: 
    #define PROCESS(_derived_, _base_)   \ 
    virtual void processNode(_derived_* node) { \ 
     processNode(static_cast<_base_*>(node)); \ 
    }  

    PROCESS(DerivedNode, BaseNode) 
    PROCESS(FurtherDerivedNode, DerivedNode) 
    PROCESS(AnotherDerivedNode, BaseNode) 

    #undef PROCESS 

}; 

class BoringNodeProcessor : public NodeProcessingInterface 
{ 
public: 
    virtual void processNode(BaseNode* node) override 
    { 
     std::cout << "It's all the same.\n"; 
    } 
}; 

class InterestingNodeProcessor : public NodeProcessingInterface 
{ 
public: 
    virtual void processNode(BaseNode* node) override 
    { 
     std::cout << "A Base.\n"; 
    } 

    virtual void processNode(DerivedNode* node) override 
    { 
     std::cout << "A Derived.\n"; 
    } 
}; 
+1

Это действительно интересная идея, спасибо. Есть ли причина, по которой 'UnifyingProcessorInterface' отличается от' NodeProcessingInterface'? Не могу ли я полностью избавиться от него, использовать 'UnifyingNodeInterface' в качестве моего« верхнего уровня »интерфейса, а затем переопределить именно те особые случаи, которые мне нужны в моих конкретных процессорах узлов (как вы это сделали)? Все конкретные процессоры должны реализовывать случай «BaseNode» и как многие производные узлы по своему усмотрению. – Ben

+1

Ну, конечно, вы можете это сделать. Не знаю, что я думал, я отредактирую код. –

+0

Отлично, это похоже на то, что я искал, еще раз спасибо. Один из них (который я надеялся избежать, но это слишком много, чтобы спросить!) Заключается в том, что если иерархия типов узлов более сложна, тогда ее нужно отражать в методах по умолчанию NodeProcessingInterface. то есть, если у меня есть DerivedDerivedNode, тогда метод 'processNode' для этого должен вернуться к методу' DerivedNode' и _ from there_ к методу для 'BaseNode'. – Ben

1

Вы правы, что не хотите проверять тип. Это нарушит Open-Closed principle - потому что каждый раз, когда вы добавляете специальный тип узла, вам придется изменить этот метод.

То, что вы описываете, похоже на архитектуру плагина, или bridge pattern.

Если вы используете наследование, а не перегрузку, то есть переместите специализированный processNode в подкласс MyNodeProcessor. Думаю, это даст вам то, что вы хотите.

EDIT:
Или, немного по-разному, вы можете сделать процессор узла классом шаблона и использовать частичную специализацию для получения желаемого поведения.

+0

* каждый раз, когда вы добавляете специализированный тип узла, вам нужно будет изменить этот метод * - это именно то, что делает шаблон посетителя, поэтому он используется в тех случаях, когда иерархия узлов сильно не изменяется, но действия сделанные на узлах. –

+0

Спасибо, я раньше не слышал о мосту. Является ли использование наследования, поскольку вы описываете существенно отличное от того, что у меня есть сейчас? Не будет ли более специализированный подкласс иметь такую ​​же проблему, что он не может выполнить контракт «MyNodeProcessor» и имеет специализированную реализацию 'processNode()'? – Ben

+0

Если вы хотите, чтобы тип параметра был специализированным, да. Хотя вы можете выполнить проверку типов в реализации производного класса и вернуться к вызову метода базового класса, если тип параметра - «BaseNode». Это все еще уродливо, но предпочтительнее проводить проверку типов в классе базового процессора. Или вы можете использовать шаблоны, которые на самом деле являются другой формой наследования, и создать частично специализированный шаблон для DerivedNode. Я имел в виду это в своем ответе, но для краткости исключил его. См. Правки. – David

0

Ну, как только дрейфует с C++, я думаю, что вы хотите назвать «категориями» в Objective C. Возможно, эта ссылка интересна: http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html

+0

Не уверен, что это полезно в этом случае, но вы правы, мне это показалось интересным , :) – Ben

Смежные вопросы