【问题标题】:Are fluent interfaces a violation of the Command Query Separation Principle?流畅的接口是否违反命令查询分离原则?
【发布时间】:2012-03-16 18:00:34
【问题描述】:

我开始编写流畅的界面,并查看了 Martin Fowler 写的关于流畅界面的旧文章(我没有意识到他和 Eric Evans 创造了这个术语)。在这篇文章中,Martin 提到 setter 通常会返回正在配置或处理的对象的实例,他说这违反了 CQS。

花括号世界中的通用约定是修饰符 方法是无效的,我喜欢它,因为它遵循的原则 命令查询分离。这个约定确实妨碍了 流畅的界面,所以我倾向于暂停这个约定 案例。

所以,如果我的流畅界面执行以下操作:

myObject
  .useRepository("Stuff")
  .withTransactionSupport()
    .retries(3)
  .logWarnings()
  .logErrors();

这真的违反了 CQS 吗?

更新我分解了我的示例,以将日志记录警告和错误显示为单独的行为。

【问题讨论】:

  • logWarningsAndErrors 是否返回任何内容?如果不是,那它真的是一个流畅的界面吗?
  • @M.Babcock 更新了我的示例。当然,logWarningsAndErrors 返回一个我可以添加其他行为的接口。

标签: c# fluent-interface command-query-separation


【解决方案1】:

是的,是的。所有这些方法显然都在返回一些东西,同样显然它们有副作用(从你没有对返回值做任何事情的事实来看,但你确实费心去调用它们)。由于 CQS 的定义规定 mutators 不应该返回值,因此我们手上有一个明确的违规行为。

但是违反 CQS 对您来说重要吗?如果流畅的界面可以让您在所有方面都更加高效,并且如果您认为它是一种众所周知的模式,具有同样众所周知的优点和缺点,那么它为什么在纸上违反原则 X 很重要?

【讨论】:

  • logWarningsAndErrors 返回的东西有多明显?
  • @M.Babcock:从技术上讲,它不是,只是从那个 sn-p。你让我到了那里。
  • 同意。如果它可以帮助您使用它,为什么它会影响原则 X。
【解决方案2】:

当它改变对象而不是只返回一个新对象时,它违反了这个原则。

var newObject = myObject
    .useRepository("Stuff")
    .withTransactionSupport()
    .retries(3)
    .logWarningsAndErrors(); 

如果myObject 在此语句之后没有变化,则一切正常。一般来说,流畅的接口违反 CQS 原则,当且仅当它有副作用。

但是问题是,如果您的示例确实代表了一个查询。 “流利”是否一定意味着“查询”?它可能只是被视为一种动作流畅的界面,其中相同的对象从一个动作传递到下一个动作。

【讨论】:

  • 嗯,但配置的重点是更改对象(取决于配置),所以为什么要麻烦呢?
  • 被修改的对象是新实例还是旧实例有什么区别?这 4 个调用中的任何一个要么修改表达式的返回值(无论是什么),要么什么都不做,因此应该省略。
  • 问题是,如果它确实修改了现有对象并同时返回结果,使其同时成为命令和查询。它应该是一个命令一个查询。
【解决方案3】:

没有。这里的模式是“配置”。此类配置命令返回配置对象本身,与与命令无关的内容相反。如果用于配置目的的命令返回了一些不相关的数据,则会发生违反命令/查询隔离的情况,例如:

if (myObject.UseRepository("Stuff") > 1 && myObject.UseRepository("Bla") < 5) {
    // oh, good, some invisible stuff internal to myObject is in right interval...
}

【讨论】:

  • 对不起,我不明白你的回答,但我想。你能扩展一下吗? “如果你有……”有在哪里?此外,您的 sn-p 中的内部状态也不会改变。我看到违反“告诉,不要问”的原则,但就是这样
【解决方案4】:

我认为这取决于这些方法在做什么。如果每个都是它自己的命令,那么是的,它可能会破坏 CQS。

但是,您可以通过 2 种不同的方式轻松解决此问题。

  1. 只是不要链接命令。只需执行 myObject.useRepository("..")。然后调用下一个,依此类推。但如果链中的下一个项目需要前一个项目的信息,那么您将遇到麻烦。

  2. 这些链接的东西并没有让它们各自成为自己的命令,而是直接更新 DTO 上的数据。然后在最后,您运行一个名为 .Configure() 的方法,然后将此 DTO 发送到执行所有处理的单个命令。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-15
    • 2010-11-29
    相关资源
    最近更新 更多