这是一种设计模式 - 从 setter 返回此模式吗?

Is this a design pattern - returning this from setters?

提问人:Luchian Grigore 提问时间:11/16/2011 更新时间:8/16/2020 访问量:587

问:

这个有名字吗:

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

所以你可以做这样的事情:

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

使用它有什么缺点吗?优势?

C++ 设计模式

评论

7赞 Steve Jessop 11/16/2011
主要缺点是您可能会以 、 和写作或(更重要的是)的形式返回。 有点垃圾;-)*thisA&A* a = new A; a->setA().setB()A b; b.setA().setB();b.setA()->setB();

答:

4赞 Flexo 11/16/2011 #1

我以前听说过它被称为“方法链”之类的东西,但我不会称它为设计模式。(有些人还谈论使用它来实现“流畅的界面”——我以前从未见过它这样称呼,但 Martin Fowler 似乎在不久前写过它)

这样做不会损失太多 - 如果您不想那样使用它,您总是可以非常愉快地忽略返回结果。

至于是否值得做,我不太确定。在某些情况下,它可能非常神秘。但是,对于基于流的 IO 之类的事情,它基本上是必需的。我想说的是,这是对它如何与代码的其余部分相适应的呼吁 - 对于阅读它的人来说,这是预期的/显而易见的吗?operator<<

(正如史蒂夫·杰索普(Steve Jessop)所指出的,这几乎总是通过引用而不是指针来完成的)

0赞 Some programmer dude 11/16/2011 #2

例如,它通常用于 Boost,但大多数情况下,函数会返回引用:

A &setX()
{
    // ...
    return *this;
}
2赞 Sean 11/16/2011 #3

一个缺点是,如果你从 A 派生一个类,可以这样说:

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

那么你打电话给 setter 的顺序就很重要了。您需要先调用 Foo 上的所有 setter: 例如,这是行不通的:

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

鉴于这将:

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

评论

0赞 Flexo 11/16/2011
好点 - 这就是为什么不可能写出这样的东西,如果它有效,这将是一个非常方便的成语。(std::ostringstream() << "hi" << 0).str()
0赞 Steve Jessop 11/16/2011
@awoodland:你能做出类似的东西吗,或者该模板是否不如现有函数匹配?template <typename T> std::ostringstream &operator<<(std::ostringstream &o, const T &t) { static_cast<std::ostream &>(o) << t; return o; }ostream&
0赞 Flexo 11/16/2011
@SteveJessop - 派生类型会比基类型更匹配,但当我刚才尝试时,它似乎不起作用。我会试着找出原因。
0赞 Flexo 11/16/2011
@SteveJessop - 我认为这有两个问题。首先,它是 的非常量引用,但会生成一个临时引用,它不能绑定到非常量引用。其次,我认为有一个没有任何模板参数,所以我认为仍然是一个更好的匹配。operator<<std::ostringstream()std::ostream& operator<<(std::ostream&, int);const char*
0赞 Steve Jessop 11/16/2011
@awoodland:这是我担心的,即使派生引用击败了基类引用,普通函数也会击败模板。
3赞 Steve Jessop 11/16/2011 #4

另一个常见的用法是“参数对象”。如果没有方法链,它们设置起来非常不方便,但有了它,它们可能是临时的。

而不是:

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();
9赞 Cheers and hth. - Alf 11/16/2011 #5

它被称为方法链接(FAQ 链接),通常使用引用而不是指针来完成。

方法链接与命名参数习惯用语FAQ 链接)密切相关,因为我现在在发布此答案的初始版本后,看到 Steve Jessop 在他的答案中进行了讨论。NPI 惯用语是一种提供大量默认参数的简单方法,而不会将复杂性强加到构造函数调用中。例如,这与 GUI 编程有关。

方法链接技术的一个潜在问题是,当您希望或需要将 NPI 惯用语应用于继承层次结构中的类时。然后你发现 C++ 不支持协变方法。这是什么:当你让你的眼睛在类继承链中向上或向下徘徊时,那么协变方法是一种定义涉及某种类型的方法,在你游荡的眼睛看来,这种类型在特定性上与它所定义的类相同。

这与定义方法的问题大致相同,该方法在所有类中都具有相同的文本定义,但必须在每个类中费力地重复才能获得正确的类型。clone

如果没有语言支持,解决这个问题是很困难的;它似乎是一个固有的复杂问题,与C++类型系统有一种冲突。我的“如何在 C++98 中键入可选参数”博客文章链接到用于自动生成协变定义的相关源代码,以及我在 Dobbs 博士杂志上写的一篇关于它的文章。也许我会在 C++11 或某个时候重新审视这一点,因为复杂性和可能的脆性可能看起来比它的价值更大......

评论

0赞 Cheers and hth. - Alf 2/23/2015
此处的常见问题解答链接应更新为 ISO CPP。我错误地拒绝了某人的编辑。(meta.stackexchange.com/questions/249938/...)