2013-08-22 44 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類的方法withUrlPostableRest類的方法withParamdoPost,和GettableRest類與doGet。我懷疑這是不可能的,並且可能會嘗試將一堆虛擬方法填入RestConnection,但是我討厭這樣做,當有多個withParam s被重載時,其中一些沒有意義包含在GET參數列表中。

在此先感謝!

+1

您可以爲Base指定一個模板參數,並且Derived從Base繼承時將其自身作爲參數傳遞。現在Base可以返回對這個模板參數的引用。 –

+1

您可能感興趣的裝飾圖案。 – 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

對不起,我只是問,因爲任何時候我看到一個演員陣容的解除我恐慌。另外,在你的示例中,「基本」(不相關)不是模板,我*認爲*你的意思是'派生類:public Base_T ',但它早在這裏對我來說知道wtf我正在走約= P(CRTP方法+1) – WhozCraig

0

看看Curiously recurring template pattern

如果Base是一個抽象類型(只在其子類中實例化),那麼使它成爲一個類型名稱的模板。您的Derive將擴展模板 - 例如Derived : public Base<Derived>。如果Base是一個具體的類型 - 那麼你將需要引入一個新的抽象類,它將成爲BaseDerived的基類型。

這種方式withFoo可以模板化返回實型。

0

你選擇要麼CRTP(馬克B中所示),或者使用運行時調度變量名稱,例如。

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

這不會特別高效,但它非常靈活,並且可以採用任意字段的協議。

+0

這其實並不差。其他答案更具體到我的問題,但我可以看到這在各種情況下都很有用,特別是在設計階段,當事情可能不穩定時。我會+1,但沒有聲譽:/ – asmodean

0

由於您的類型不是多態(沒有虛函數),因此Base沒有派生的知識。

你可以達到你的目標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 instanciation一樣,都是可能的派生,因此你不能再有一個基地&或Base *指向任何Derived。

如果你需要一個共同的基地來收集,你需要另一個基地可以從中導出的CommonBase(沒有寺廟化)。

另一種可能性是通過虛擬withFoo使Base(舊的)變成多態。 在這一點上,在派生,可以覆蓋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)); } 
    ... 
}; 

這仍然擁抱經典OOP範式,但增加了運行時開銷(的虛函數表),並具有以下缺點:它基於一個特性(協變返回類型的虛函數)並非所有編譯器都支持。