2011-11-16 67 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();