2011-11-16 64 views
7

是否有這樣一個名字:這是一個設計模式 - 從setters返回這個?

class A 
{ 
    A* setA() 
    { 
     //set a 
     return this; 
    } 
    A* setB() 
    { 
     //set b 
     return this; 
    } 
}; 

所以你可以做這樣的事情:

A* a = new A; 
a->setA()->setB(); 

是否有任何缺點使用這個?優點?

+7

主要缺點是,你可能會返回'* this'作爲'A&'和'寫A * A =新的A。 a-> setA().setB()'或(更重要的)'A b; 。b.setA()組B();'。 'b.setA() - > setB();'有點垃圾;-) –

回答

9

它被稱爲方法鏈FAQ link),以及更常見於引用,不是指針完成。

方法鏈接強烈與命名參數成語(FAQ link)的相關的,就像我現在,張貼此答案的初始版本之後,看到Steve Jessop discusses in his answer。 NPI習慣用法是一種簡單的方法,可以在不強制構造函數調用的情況下提供大量缺省參數。例如,這與GUI編程有關。

方法鏈接技術的一個潛在問題是當您需要或需要在繼承層次結構中爲類應用NPI習語時。然後你發現C++不支持協變方法。那是什麼:當你讓你的眼睛在類繼承鏈中上下滑動時,那麼協變方法就是這樣一種方法,它的定義涉及到某種類型,對於你的流浪眼來說,它的特異性與其類相同,類似於它的類型’定義在

這與定義clone方法的方法大致相同,該方法在所有類中具有相同的文本定義,但必須在每個類中費力地重複才能獲得正確的類型。

解決這個問題很難,沒有語言支持;它似乎是一個內在複雜的問題,是一種與C++類型系統的衝突。我的「How to do typed optional arguments in C++98」 blog post鏈接到相關源代碼以自動生成協變定義,以及我在Dr. Dobbs Journal上撰寫的一篇文章。也許我會重新考慮C++ 11,或者某個時候,因爲複雜性和可能的​​脆弱性可能會比它值得的成本更高。

乾杯&心連心,

+0

此處的FAQ鏈接應更新爲ISO CPP。我錯誤地拒絕了某人的編輯來執行此操作。 (http://meta.stackexchange.com/questions/249938/i-incorrectly-rejected-some-edits-what-to-do) –

4

我以前聽說過它叫做「method chaining」,但我不會稱之爲設計模式。 (有些人也談到使用這個實現「fluent interface」 - 雖然我從來沒有見過這種情況,但Martin Fowler似乎已經寫了一段時間了)

你不會因此丟失太多 - 如果你不想那樣使用它,你可以總是很高興地忽略返回的結果。

至於是值得去做,我不太確定。在某些情況下,它可能相當神祕。但基本上,對於像基於流的IO的operator<<之類的東西,基本上需要。我想說這是一個關於它如何適應其他代碼的呼籲 - 對於閱讀它的人來說是否預期/顯而易見?

0

它通常用於例如升壓(正如史蒂夫·傑索普指出,這是幾乎總是與引用雖然不是指針完成),但大部分時間的函數返回的引用來:

A &setX() 
{ 
    // ... 
    return *this; 
} 
2

一個缺點是,如果你從一個派生類,這樣說:

class Foo : public A 
{ 
public: 
    Foo *setC() 
    { 
    // set C 
    return this; 
    } 
}; 

然後調用制定者的順序是非常重要的。你需要先請美孚所有的setter方法:例如,這是不行的:

Foo f=new Foo(); 
f->setA()->setC(); 

鑑於此意願:

Foo f=new Foo(); 
f->setC()->setA(); 
+0

好點 - 這就是爲什麼不可能寫類似'(std :: ostringstream()<<「hi」<< 0 ).str()'這將是一個非常方便的習慣用法,如果它工作。 – Flexo

+0

@awoodland:你可以做一些像'template std :: ostringstream&operator <<(std :: ostringstream&o,const T&t){static_cast (o)<< t;返回o; }',還是那個模板比'ostream&'現有的函數不太好? –

+0

@SteveJessop - 派生類型會比基類型更好匹配,但當我剛纔嘗試時它似乎不起作用。我會試着找出原因。 – Flexo

3

另一個常見上下的使用方法與「參數對象」。如果沒有方法鏈接,他們設置起來很不方便,但是用它可以是臨時的。

相反的:

complicated_function(P1 param1 = default1, P2 param2 = default2, P3 param3 = default3); 

寫:

struct ComplicatedParams { 
    P1 mparam1; 
    P2 mparam2; 
    P3 mparam3; 
    ComplicatedParams() : mparam1(default1), mparam2(default2), mparam3(default3) {} 
    ComplicatedParams &param1(P1 p) { mparam1 = p; return *this; } 
    ComplicatedParams &param2(P2 p) { mparam2 = p; return *this; } 
    ComplicatedParams &param3(P3 p) { mparam3 = p; return *this; } 
}; 

complicated_function(const ComplicatedParams &params); 

現在,我可以把它叫做:

complicated_function(ComplicatedParams().param2(foo).param1(bar)); 

這意味着調用者沒有記住參數的順序。如果沒有,將有方法鏈接是:

ComplicatedParams params; 
params.param1(foo); 
params.param2(bar); 
complicated_function(params); 

我也可以把它叫做:

complicated_function(ComplicatedParams().param3(baz)); 

這意味着無需定義過載一噸,我可以指定剛剛過去的參數和默認情況下保留其餘部分。

最後明顯的調整就是讓complicated_functionComplicatedParams成員:

struct ComplicatedAction { 
    P1 mparam1; 
    P2 mparam2; 
    P3 mparam3; 
    ComplicatedAction() : mparam1(default1), mparam2(default2), mparam3(default3) {} 
    ComplicatedAction &param1(P1 p) { mparam1 = p; return *this; } 
    ComplicatedAction &param2(P2 p) { mparam2 = p; return *this; } 
    ComplicatedAction &param3(P3 p) { mparam3 = p; return *this; } 
    run(void); 
}; 

ComplicatedAction().param3(baz).run();