流利的接口是否违反了命令查询分离原则

本文关键字:命令 查询 分离 原则 接口 是否 | 更新日期: 2023-09-27 18:27:59

我开始写一个流利的界面,并看了马丁·福勒写的一篇关于流利界面的旧文章(我没有意识到他和埃里克·埃文斯创造了这个词)。在这篇文章中,Martin提到setter通常返回正在配置或处理的对象的实例,他说这违反了CQS。

大括号世界中常见的约定是方法是无效的,我喜欢它,因为它遵循CommandQuerySeparation。这个惯例确实妨碍了流利的界面,所以我倾向于为此暂停惯例案例

所以,如果我的流畅界面做了一些类似的事情:

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

这真的违反了CQS吗?

UPDATE我分解了我的示例,将日志警告和错误显示为单独的行为。

流利的接口是否违反了命令查询分离原则

是的。所有这些方法显然都在返回一些东西,同样明显,它们也有副作用(从你没有对返回值做任何事情来看,但你确实很麻烦地调用它们)。由于CQS的定义规定突变不应返回值,我们有一个明确的违规行为。

但是违反CQS对你来说重要吗?如果流畅的界面让你在所有考虑的事情上都更有效率,如果你认为它是一个众所周知的模式,有着同样众所周知的优点和缺点,为什么应该它违反了纸上的原则X?

当它更改对象时违反了这一原则,但当它只返回一个新对象时却没有。

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

如果myObject在这句话之后没有变化,那么一切都是好的。一般来说,一个流利的接口违反了CQS原则,当且仅当它有副作用。

然而,问题是,如果您的示例确实表示了一个查询。"流利"一定意味着"查询"吗?它可能只是被视为一个动作流畅的界面,在这个界面中,相同的对象从一个动作传递到下一个动作。

否。这里的模式是"配置"。这样的配置命令返回的配置对象本身与命令无关。如果用于配置目的的命令返回了一些不相关的数据,则会违反命令/查询分离,例如:

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

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

然而,你可以通过两种不同的方式轻松解决这个问题。

  1. 只是不要连锁命令。只需执行myObject.useRepository("..")。然后调用下一个,等等。但是,如果链中的下一个项目需要上一个项目的信息,那么您就会遇到麻烦。

  2. 相反,这些链接的东西只是直接更新DTO上的数据,而不是让每个都有自己的命令。最后,运行一个名为.Configure()的方法,然后将此DTO发送给一个执行所有处理的命令。

如果忽略类型系统DSL设计,fluent接口与方法链接完全相同。

o.A().B()相当于a = o.A(); a.B()

它确实违反了可变数据结构中的命令-查询分离。在方法实现中,我们必须显式地添加多余的return this(这里ao指的是同一对象)(顺便说一句,在这种情况下,我更喜欢级联方法)

然而,我们也经常在不可变的数据结构中看到它,因为纯函数必须返回结果。(这里ao指不同的对象`)在这种情况下,它不违反命令-查询分离