2013-08-22 5 views
1

Возможно ли писать свободно используемые методы chanining, возвращающие производный тип? Рассмотрим следующие два класса:метод цепочки с полиморфизмом C++

class Base { 
protected: 
    std::string mFoo; 
public: 
    Base& withFoo(std::string foo) { 
     mFoo = foo; 
     return *this; 
    } 
}; 

class Derived : public Base { 
protected: 
    std::string mBar; 
public: 
    Derived& withBar(std::string bar) { 
     mBar = bar; 
     return *this; 
    } 

    void doOutput() { 
     std::cout << "Foo is " << 
      mFoo << ". Bar is " << 
      mBar << "." << std::endl; 
    } 
}; 

Я тогда хотел бы построить свой объект и использовать его как это:

Derived d; 
d.withFoo("foo").withBar("bar").doOutput(); 

Это, конечно, не удается, так как withFoo возвращает Base. Поскольку все мои методы with просто устанавливают переменные-члены, я могу сначала указать производные with. Проблема заключается в том, что мой метод построения (doOutput в приведенном выше примере) должен быть отдельным заявлением.

Derived d; 
d.withBar("this is a bar") 
    .withFoo("this is my foo"); 
d.doOutput(); 

Мой вопрос, есть ли какой-нибудь способ для withFoo вернуть неизвестный производный тип, так что Base может быть использован без проблем с несколькими производными классами (в конце концов, *thisявляетсяDerived, хотя Base (правильно) не знает об этом).

Для более конкретного примера я пишу несколько классов для доступа к серверу REST. У меня есть класс RestConnection с методом withUrl, a PostableRest класс с методами withParam и doPost и класс GettableRest с doGet. Я подозреваю, что это невозможно, и, вероятно, попробуем переделать кучу виртуальных методов в RestConnection, но я не люблю это делать, когда перегружено несколько withParam s, некоторые из которых не имеют смысла включать в список параметров GET.

Заранее благодарен!

+1

Вы можете указать базовый параметр шаблона, а Derived передать себя как параметр, когда он наследует базу. Теперь Base может вернуть ссылку на этот параметр шаблона. –

+1

Вас может заинтересовать Decorator pattern. – Jarod42

+0

Вам также нужен полиморфизм времени выполнения от 'Base' до различных производных классов? Также обратите внимание, что в целом защищенные атрибуты (а не методы) являются сильным дизайнерским запахом, поскольку они допускают легкое нарушение инвариантов. –

ответ

3

Я думаю, вы могли бы использовать CRTP здесь, что-то вроде следующего, где производный класс говорит основание, какой тип он является:

class Base 
{ 
    // Abstract/virtual interface here. 
}; 

template <class Derived> 
class Base_T : public Base 
{ 
private: 
    std::string mFoo; 

public: 
    Derived& withFoo(std::string foo) { 
     mFoo = foo; 
     return *static_cast<Derived*>(this); 
    } 
}; 

class Derived : public Base_T<Derived> { 
private: 
    std::string mBar; 
public: 
    Derived& withBar(std::string bar) { 
     mBar = bar; 
     return *this; 
    } 

    void doOutput() { 
     std::cout << "Foo is " << 
      mFoo << ". Bar is " << 
      mBar << "." << std::endl; 
    } 
}; 
+0

Марк, вы рекомендуете 'static_cast' в этом случае против объявления виртуального деструктора базы и использования' dynamic_cast (* this) '? Что-то вроде этого (http://ideone.com/ftLrP7), в качестве примера. – WhozCraig

+0

@WhozCraig Я абсолютно рекомендую 'static_cast', потому что наследование гарантирует, что тип будет правильным во всех случаях. 'dynamic_cast' добавит накладные расходы без каких-либо дополнительных преимуществ. –

+0

Извините, я просил только потому, что в любое время, когда вижу разницу в операции литья, я паникую. Кроме того, (не связанный) в вашем примере 'Base' не является шаблоном, я * думаю * вы имели в виду' class Derived: public Base_T ', но его слишком рано утром здесь для меня, чтобы узнать wtf, я иду = P (+1 для CRTP-подхода) – WhozCraig

0

Посмотрите на Curiously recurring template pattern.

Если Base является абстрактным типом (создается только в его подклассах), то создайте шаблон, используя имя типа. Ваш Derive расширит шаблон - например. Derived : public Base<Derived>. Если Base - это конкретный тип, то вам нужно будет ввести новый абстрактный класс, который был бы базовым типом для Base и Derived.

Этот способ withFoo может быть шаблоном для возврата реального типа.

0

Вам варианты либо CRTP (как показано на рисунке Mark B), или с помощью диспетчеризация времени выполнения на имя переменной, например.

Derived d; 
d.with("Foo", "foo").with("Bar", "bar").doOutput(); 

Это не будет особенно результативным, но оно очень гибкое и подходит для протоколов, которые могут принимать произвольные поля.

+0

Это не плохо. Другие ответы более специфичны для моей проблемы, но я вижу, что это полезно в самых разных ситуациях, особенно на этапе проектирования, когда ситуация может произойти. Я бы добавил +1, но не имею репутации: / – asmodean

0

Поскольку ваши типы не являются полиморфными (нет виртуальных функций), база не имеет знака производного.

Вы можете связаться с вами цель esentially со статическим полиморфизмом:

template<class Derived> 
class Base { 
protected: 
    std::string mFoo; 
public: 
    Derived& withFoo(std::string foo) { 
     mFoo = foo; 
     return static_cast<Derived&>(*this); 
    } 
}; 

class Derived : public Base<Derived> { 
protected: 
...... 
} 

Недостаток заключается в том, что существует больше Base класса, но так как многие Base при создании экземпляра, как и возможные полученные, следовательно, вы не можете больше иметь базу & или Base *, указывающий на то, что Производится.

Если вам нужна общая база для сбора, вам понадобится другая CommonBase (не храмовая), из которой может быть получена Base.

Другая возможность - сделать базовую (старую) полиморфную, сделав withFoo виртуальной. В этот момент в Derived, вы можете переопределить withFoo вернуть Производный тип & ковариантный:

class Base 
{ 
    ... 
    virtual Base& withFoo(...); 
    ... 
    virtual ~Base() {} //just to make all the hierarchy destructible through base 
}; 

class Derived: public Base 
{ 
    ... 
    virtual Derived& withFoo(type arg) 
    { return static_cast<Derived&>(Base::withFoo(arg)); } 
    ... 
}; 

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

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