首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >CQS设计原理问题:队列的实现

CQS设计原理问题:队列的实现
EN

Stack Overflow用户
提问于 2018-11-10 10:22:49
回答 2查看 268关注 0票数 3

我创建这个问题是基于我在对这个问题的答案:设计一个返回值或更改某些数据的方法,但不能同时更改两者的评论中进行的一个小小的讨论。

@Kata指出OP感兴趣的模式称为命令-查询分离,并认为这是构建代码的一个很好的模型。

来自维基百科

命令-查询分离(CQS)是命令式计算机编程的一项原则.它是由Bertrand设计的,这是他在埃菲尔编程语言方面的开创性工作的一部分。 它指出,每个方法要么是执行操作的命令,要么是将数据返回给调用者的查询,而不是两者都返回。换句话说,问一个问题不应该改变答案。1更正式地说,方法只有在引用透明并因此没有副作用的情况下才能返回值。

我对这个设计原则的可靠性提出了质疑,因为一般来说,它会使您的代码变得更加乏味。例如:您不能执行像next =Queue.Dequeue()这样的简单语句;需要两个指令:一个用于修改数据结构,另一个用于读取结果。

@Kata找到了一种替代的Stack实现,乍一看似乎满足了这两个世界的最佳条件:从函数式编程中获取一个页面,我们将我们的Stack定义为一个不变的数据结构。每当我们推送(X)时,我们就创建一个新的Stack节点,它保存值x,并维护一个指向旧instance实例的指针。每当我们弹出()时,我们只返回指向下一个Stack实例的指针。因此,我们可以坚持命令-查询分离原则。

示例堆栈实现:https://fsharpforfunandprofit.com/posts/stack-based-calculator/

但是,在这种情况下还不清楚的是,如何保持对堆栈的多个引用保持同步,同时仍然坚持命令-查询分离原则?我看不出有什么明显的解决办法。因此,出于好奇,我向社区提出了这个问题,看看我们是否能找到令人满意的解决办法:)

编辑:下面是问题的一个例子:

代码语言:javascript
复制
s = new Stack();
s2 = s
...
s = s.push(x);
assert(s == s2); // this will fail
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2018-11-11 07:38:34

在函数式编程(,FP)风格中,我们通常设计我们的函数,这样就不需要保持这些引用保持同步。

考虑以下情况:创建堆栈s,将其注入Client对象,然后将项推送到s并获得新的堆栈s2

代码语言:javascript
复制
s = new Stack()
client = new Client(s)
s2 = s.push(...)

因为ss2不同步(即它们是不同的堆栈),所以在对象client中,它仍然看到堆栈(s)的旧版本,这是您不想要的。这是Client的代码

代码语言:javascript
复制
class Client {
    private Stack stack;
    // other properties
    public Client(Stack stack) { this.stack = stack; }
    public SomeType foo(/*some parameters*/) {
        // access this.stack
    }
}

为了解决这个问题,函数方法不使用这样的隐式引用,而是将引用作为显式参数传递到函数中:

代码语言:javascript
复制
class Client {
    // some properties
    public SomeType foo(Stack stack, /*some parameters*/) {
        // access stack
    }
}

当然,有时这会很痛苦,因为函数现在有一个额外的参数。Client的每个调用方都必须维护一个堆栈,以便调用foo函数。这就是为什么在FP中,您倾向于看到比OOP中参数更多的函数。

但是FP有一个可以减轻这种痛苦的概念:所谓的部分适用。如果已经有了堆栈s,则可以编写client.foo(s)以获得foo的“升级”版本,该版本不需要堆栈,但只需要其他some parameters。然后,您可以将升级的foo函数传递给不维护任何堆栈的接收方。

然而,值得一提的是,有些人认为这种痛苦实际上是有帮助的。例如,Scott在他的文章依赖注入的功能方法

当然,缺点是这个函数现在有五个额外的参数,这看起来很痛苦。(当然,OO版本中的等效方法也有这五个依赖项,但它们是隐式的)。 在我看来,这种痛苦其实是有帮助的!有了OO风格的接口,随着时间的推移,它们自然会产生结块。但是,有了这样的显式参数,就有了一种自然的抑制因素--有太多的依赖!对诸如接口隔离原则这样的指导方针的需要大大减少了。

此外,马克·西曼( Mark ) --这本书的作者--在依赖排斥上有一个有趣的系列。

如果您不能忍受这种痛苦,那么只需打破CQS,回到传统的Stack实现。毕竟,如果一个函数(如pop/dequeue)是众所周知的,并且清楚地知道它既返回了一些东西,又改变了它的内部数据,那么违反CQS并不是那么糟糕。

即使在这种情况下,一些FP语言也提供了消息传递机制,这样就可以以不编写代码变异数据的方式实现可变堆栈(例如,使用赋值符号的代码)。MailboxProcessor in F#就是这样一种机制。

希望这会有所帮助:)

票数 3
EN

Stack Overflow用户

发布于 2018-11-10 14:20:50

由于函数的设计,需要返回反映上下文的状态。

这允许您区分成功和失败,以及潜在的其他信息,如果您在下面的最小方代码中为bool补充了DequeueResult。

让Dequeue =函数bool(结果)

如果校长== Null回来..。返回真

更符合CQS的内容可能是

让Dequeue =函数Node()返回头

但是需要Head有一个特殊的Node.Null值来尝试区分失败和争用。

返回一个DequeueResult可能会更好,您可以在结果中更多地说明故障。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/53237990

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档