CQS设计原则问题:实现队列 [英] CQS Design Principle Problem: Implementing a Queue

查看:155
本文介绍了CQS设计原则问题:实现队列的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在基于对这个问题的答案的评论中的一小段讨论来创建此问题:

I'm creating this question based upon a small discussion I had in the comments to the answer to this question: design a method returning a value or changing some data but not both

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

@Kata pointed out that the pattern that the OP was interested in is called Command–query separation and argued that this is a good model to structure your code by.

来自维基百科:

命令查询分离(CQS)是命令式计算机编程的原理.它是由Bertrand Meyer设计的,是他在Eiffel编程语言方面的开创性工作的一部分.

Command–query separation (CQS) is a principle of imperative computer programming. It was devised by Bertrand Meyer as part of his pioneering work on the Eiffel programming language.

它指出,每个方法都应该是执行操作的命令,或者是将数据返回给调用方的查询,但不能同时是两者.换句话说,提出问题不应改变答案. 1 更正式地讲,方法仅在引用透明且没有副作用的情况下才应返回值.

It states that every method should either be a command that performs an action, or a query that returns data to the caller, but not both. In other words, Asking a question should not change the answer.1 More formally, methods should return a value only if they are referentially transparent and hence possess no side effects.

我对这种设计原则的合理性提出了质疑,因为总体而言,它似乎会使您的代码更加乏味.例如:您无法执行像 next = Queue.Dequeue(); 这样的简单语句.您将需要两条指令:一条用于修改数据结构,一条用于读取结果.

I questioned the soundness of this design principle, as in general it would appear to make your code far more tedious. For instance: you could not perform a simple statement like next = Queue.Dequeue(); You would need two instructions: one to modify the data structure and one to read the result.

@Kata发现了一种替代的Stack实现,乍一看似乎满足了两个方面的最佳要求:从函数式编程中获取一页,我们将Stack定义为一个不变的数据结构.每当我们push(x)时,我们都会创建一个新的Stack节点,该节点保存值x并维护指向旧头Stack实例的指针.每当我们pop()时,我们仅将指针返回到下一个Stack实例.因此,我们可以遵守命令查询分离原则.

@Kata found an alternative Stack implementation that at first glance appears to satisfy the best of both worlds: taking a page from functional programming, we define our Stack as an immutable data-structure. Whenever we push(x) we create a new Stack node that holds the value x and maintains a pointer to the old head Stack instance. Whenever we pop() we just return the pointer to the next Stack instance. Thus we can adhere to the Command-Query Separation Principle.

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

但是,在这种情况下还不清楚的一件事是,如何在仍然遵循命令查询分离原理的情况下使对堆栈的多个引用保持同步?我没有看到一个明显的解决方案.因此,出于好奇,我正在向社区提出此问题,以查看是否找不到令人满意的解决方案:)

However, one thing that is unclear in this case is how you would keep multiple references to the Stack in sync while still adhering to the Command-Query Separation Principle? I don't see an obvious solution to this. So as a point of curiosity I'm posing this problem to the community to see if we can't find a satisfactory solution :)

这是问题的一个示例:

s = new Stack();
s2 = s
...
s = s.push(x);
assert(s == s2); // this will fail

推荐答案

在函数式编程(FP)样式中,我们经常设计函数,以便无需使这些引用保持同步.

In Functional Programming (FP) style, we often design our functions so that we don't need to keep such those references in sync.

请考虑以下情形:创建一个堆栈s,将其注入到Client对象中,然后将项目推入s并获得一个新的堆栈s2:

Consider this scenario: you create a stack s, inject it into a Client object, then push an item to s and get a new stack s2:

s = new Stack()
client = new Client(s)
s2 = s.push(...)

因为ss2不同步(即它们是不同的堆栈),所以在对象client内部,它仍然看到堆栈的旧版本(s),您不会这样做.不想.这是Client的代码:

Because s and s2 are not in sync (i.e., they are different stacks), inside the object client, it still sees the old version of stack (s) which is something you don't want. Here is the code of Client:

class Client {
    private Stack stack;
    // other properties
    public Client(Stack stack) { this.stack = stack; }
    public SomeType foo(/*some parameters*/) {
        // access this.stack
    }
}

为解决此问题,功能方法不使用此类隐式引用,而是将引用作为显式参数传递给函数:

To solve this, the functional approach does not use such an implicit reference, but instead passes the reference into the function as an explicit parameter:

class Client {
    // some properties
    public SomeType foo(Stack stack, /*some parameters*/) {
        // access stack
    }
}

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

Of course, sometimes this would be painful as the function now has one extra parameter. Every caller of Client must maintain a stack in order to call the foo function. That's why in FP you tend to see functions with more parameters than in OOP.

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

But FP has a concept which can mitigate this pain: the so-called partial application. If you already have a stack s, you can write client.foo(s) to get an "upgraded" version of foo which does not require a stack but only requires the other some parameters. Then you can pass that upgraded foo function to a receiver who does not maintain any stack.

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

Nevertheless, it's worth to mention that there are people who agure that this pain can be actually helpful. For example, Scott Wlaschin in his article Functional approaches to dependency injection:

当然,缺点是该函数现在有五个额外的参数,这看起来很痛苦. (当然,OO版本中的等效方法也具有这五个依赖关系,但它们是隐式的.)

The downside of course, is that there are now five extra parameters for the function, which looks painful. (Of course, the equivalent method in the OO version also had these five dependencies, but they were implicit).

尽管在我看来,这种痛苦实际上是有帮助的!使用OO风格的接口,它们自然会随着时间的推移而增加粗粒.但是,使用像这样的显式参数,自然就会抑制太多的依赖!对诸如接口隔离原理之类的指南的需求已大大减少.

In my opinion though, this pain is actually helpful! With OO style interfaces, there is a natural tendency for them to accrete crud over time. But with explicit parameters like this, there is a natural disincentive to have too many dependencies! The need for a guideline such as the Interface Segregation Principle is much diminished.

此外,马克·西曼(Mark Seemann)-这本书的作者相关性拒绝.

Also, Mark Seemann -- the author of the book Dependency Injection -- had an interesting series on Dependency Rejection.

万一您不能忍受那种痛苦,那就打破CQS并回到传统的Stack实现.毕竟,如果一个函数(如pop/dequeue)是众所周知的,并且知道它既返回内容又更改其内部数据,那么违反CQS的情况就不是那么糟糕了.

In case you cannot suffer that pain, then just break the CQS and back to the traditional implementation of Stack. After all, if a function (like pop/dequeue) is well-known and well-aware that it both returns something and changes its internal data, a violation of CQS is not so bad.

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

Even in this case, some FP languages offer a message-passing mechanism so that you can implement a mutable Stack in a manner that you don't write code mutating data (e.g., code using assignment symbol). MailboxProcessor in F# is such a mechanism.

希望这会有所帮助:)

这篇关于CQS设计原则问题:实现队列的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆