admin管理员组

文章数量:1597416

物化视图和命令查询职责分离

什么是CQS / CQRS(命令和查询分离)?(What is CQS/CQRS (Command & Query Separation)?)

First, let’s agree that each method of a class can do one or both things:

首先,让我们同意一个类的每个方法可以做件事或件事:

  1. It can mutate a private value of the class (hence you tell the object to mutate it, therefore it is also called a “Command”).

    它可以更改类的私有值(因此,您告诉对象对其进行更改,因此也称为“命令”)。
  2. It can return an object/value as a result or additional data (hence you want to query the object to tell you something).

    它可以返回结果的对象/值或其他数据(因此您要查询该对象以告诉您一些信息)。

Let’s see some examples (TypeScript):

让我们看一些示例(TypeScript):

Don’t worry about the long class, you can instantly skip to the methods explanation right beneath it.

不用担心冗长的类,您可以立即跳到其下面的方法说明。

class MyNumber
{
    private innerInteger: number = 0;
    ...
    ...
    ...


    // This is a command, you ask the object of class 'Number' to Increment
    // it's value, notice that it does not return anything, simply mutating
    // inner properties and nothing else.
    public increment(): void
    {
        this.innerInteger++;
    }


    // This is a query, you ask the object of class 'Number' for some information.
    // Notice that it does not change/mutate any inner properties, this is a simple get method.
    // although it can do any computation it see fits to do so.
    public value(): number
    {
        return this.innerInteger;
    }


    // Another example for query,
    public isLargerThan(other: MyNumber): boolean
    {
        return (this.value() > other.value())
    }


    // This is an example for a method that does both.
    // You command the object to change it's inner value to some other value
    // AND query it to tell you if the value was changed at all.
    public changeTo(other: MyNumber): boolean
    {
        if (this.value() == other.value())
            return false;
        
        this.innerInteger = other.value();
        return true;
    }
}

Let’s look at methods by category:

让我们按类别查看方法:

Command:

命令:

public increment(): void
{
    this.innerInteger++;
}

You can see that we ask the object to do something, we command it to mutate and increment its inner value by 1.

可以看到,我们要求对象执行某项操作,命令它进行变异并将其内部值增加1。

Query:

查询:

The following method is a simple ‘gettter’ of the property innerInteger, we ask the object to tell us things (what is your value?), and not do things.

下面的方法是属性innerInteger的简单“ getter” 我们要求对象告诉我们事情(您的价值是什么?),而不是做事情。

public value(): number
{
   return this.innerInteger;
}

As the method is a simple ‘getter’ it is a bit hard to understand the concept of query, so I’ve written another method that simply asks the object “are you larger than the other object?”.

由于该方法是一个简单的“获取器”,因此很难理解查询的概念,因此我编写了另一种方法,只是询问对象“您比另一个对象大吗?”。

public isLargerThan(other: MyNumber): boolean
{
   return (this.value() > other.value())
}

Both Command & Query:

命令和查询:

The function ‘changeTo’ as its name implies, tells the object to change its value to some other value, but only in case it does not already have that value, and then it returns (it tells us) if the value was indeed changed or not.

顾名思义,函数“ changeTo”告诉对象将其值更改为其他值,但前提是该对象尚未具有该值,然后返回(告诉我们)该值是否确实已更改或不。

public changeTo(other: MyNumber): boolean
{
   if (this.value() == other.value())
      return false;
      
   this.innerInteger = other.value();
   return true;
}

So, what about separation? the CQS principle says that we should separate commands from queries, i.e. each method should be or a command that mutates inner data, or a query which returns data and tell us things, but not both.

那么,分离呢? CQS原则说,我们应该将命令与查询分开,即,每个方法都应该是或使内部数据变异的命令,或者是返回数据并告诉我们事情的查询,但不能同时使用这两种方法。

Why? and if that so, what’s wrong with the ‘changeTo’ method?

为什么? 如果是这样,“ changeTo”方法有什么问题?

CQS principle is all about transparency of what that method do, and about separating methods that mutate the state from the methods that does not. Many reputable authors describe the state as the root of all evil of programming errors, especially Yegor256, a pure object-oriented-paradigm advocate, or how he likes to call it: Elegant-Objects, where he calls CQS as a pattern of “Builders and Manipulators”

CQS原则全是关于该方法执行的透明性,以及将使状态发生突变的方法与不更改状态的方法分离开来。 许多著名的作者都将状态描述为所有编程错误的根源,尤其是Yegor256 (纯粹的面向对象范式倡导者)或他喜欢如何称呼它: Elegant-Objects ,他称CQS为“ Builders ”模式和操纵器”

Why is mutating state so bad? I wouldn’t say it is bad, most of the times it is even inevitable to not mutate state, but state makes many things unpredictable, state can make two calls to the same method at different occasions to return or do different things, which makes it hard to test, look at the following example:

为什么突变状态如此糟糕? 我不会说这很糟糕,在大多数情况下,不改变状态甚至是不可避免的,但是状态会使许多事情变幻莫测,状态可以在不同场合对同一方法进行两次调用以返回或执行不同的事情,这使得很难测试,看下面的例子:

let firstNumber : MyNumber = new MyNumber(5);
let secondNumber : MyNumber = new MyNumber(4);


// will print 'True'
console.log(firstNumber.isLargerThan(secondNumber)); 


// Changing the state of secondNumber
secondNumber.increment();
secondNumber.increment();


// will print 'False'
console.log(firstNumber.isLargerThan(secondNumber));

The same query method ‘isLargerThan’ return two different results because the state is altered, the result could not be changed if the state wasn’t mutated, that is one of the reason that in multi-threaded programs the functional-programming paradigm takes a lead over object-oriented.

相同的查询方法'isLargerThan'返回两个不同的结果,这是因为状态已更改,如果状态未发生突变则无法更改结果,这是在多线程程序中函数编程范例采用领导面向对象。

So by separating commands from queries, we now have set of methods, called queries that we can call as many times as we like without worry that something might go wrong with the object because something is changed inside.

因此,通过将命令与查询分开,我们现在有了一组称为查询的方法,我们可以根据需要多次调用它们,而不必担心由于内部的某些更改而导致对象出了问题。

The problem with ‘changeTo’ method is not about mutating state, as its purpose is more of a command than query, but it lacks transparency, when a programmer sees the method for the first time, he reads the name ‘changeTo’ and get the idea that this method is a command that changes some value, but how could he know that it returns anything? and if it does return something, what does it return and why? the method name does not make it clear at first glance all the things that the method does, in fact, it does two things, mutating and returning its result, which people might be thinking that it could be a violation of the Single-Responsibility principle (SRP), but it can be confusing as the SRP talks about separating different axes of change and not different logic, but the same idea applies here, we want to separate the methods that mutates the state, and those who don’t.

“ changeTo”方法的问题不在于改变状态,因为它的目的更多地是命令而不是查询,但它缺乏透明性,当程序员第一次看到该方法时,他会读取名称“ changeTo”并获得认为此方法是更改​​某些值的命令,但是他怎么知道它会返回任何值? 如果它确实返回了某些东西,它将返回什么,为什么? 方法名称乍一看并不能使所有事情都一目了然,实际上,它做了两件事,就是变异并返回结果,人们可能会认为这可能违反了单一职责原则。 (SRP),但是当SRP谈论分离不同的变化轴而不是不同的逻辑时,这可能会造成混淆,但是同一思想在这里适用,我们想要分离改变状态的方法和不改变状态的方法。

In pure object-oriented style, where everything is an object, we can perhaps introduce a new ‘ChangeRequest’ class which will store the result if the value was indeed changed and also execute the request.

在所有对象都是对象的纯面向对象风格中,我们也许可以引入一个新的“ ChangeRequest”类,如果确实更改了值,该类将存储结果并执行请求。

let number : MyNumber = new MyNumber(3);
let req : ChangeRequest = number.changeTo(new MyNumber(11));
req.execute(); // here is where the actual change happens.
console.log(req.isChanged()) // will print 'true'

Notice that this type is more of declarative then imperative as we don’t immediately change the value at ‘changeTo’ method, instead we delegates the work to some other request class that can be executed when we see fits, which handles everything about the ChangeRequest such as executing it and storing its result, ‘changeTo’ now becomes a query (or a builder by Yegor) which returns an object of type ChangeRequest that has methods of ‘execute’ which is a command and ‘isChanged’ as query.

请注意,此类型更多是声明性的,而并非命令性的,因为我们不立即更改'changeTo'方法的值,而是将工作委托给其他一些可以在合适时执行的请求类,该类可以处理有关ChangeRequest的所有操作例如执行它并存储其结果,“ changeTo”现在成为一个查询(或Yegor的构建器),该查询返回类型为ChangeRequest的对象,该对象具有作为命令的“执行”和“ isChanged”方法。

Some people would say that if these 4 lines of code above were written inside a method, they would violate the Law of Demeter (LoD) as ‘req’ of type ChangeRequest is created by another object ‘number’ of type ‘MyNumber’ that probably was passed to the method as an argument or stored as a private field inside the class, and by executing ‘req.execute()’ the violation is inevitable.

有人会说,如果上面的四行代码是在一个方法中编写的,则它们将违反Demeter (LoD)法则,因为ChangeRequest类型的“ req”是由另一个类型为“ MyNumber”的对象“ number”创建的被作为参数传递给该方法或存储为类内部的私有字段,并且通过执行'req.execute()'不可避免。

Someone once asked me, if CQS principle is so important, then why many people still don’t use it? take for example Java’s Set data structure with the method ‘add’:

曾经有人问我,如果CQS原理如此重要,那么为什么还有很多人不使用它呢? 以方法为“ add”的Java的Set数据结构为例:

Set<String> myAwesomeSet = new HashSet<String>();
...


if (myAwesomeSet.add("Neyney"))
    system.out.println('A new String was added!');

In Java, the ‘add’ method of Set is a command and also a query, it adds a new item to its internal data structure (hence mutating its state) and also returns boolean value true/false if the item was already present in the set or not, as was said before, it lacks transparency, a programmer reading these lines would have to consult with the documentation to know what ‘add’ returns, because the name of the method does not imply anything that should be returned by it, I’m just adding a new string “Neyney” to the set right? what should I expect in return?

在Java中,Set的'add'方法既是命令又是查询,它将新项目添加到其内部数据结构中(因此会改变其状态),并且如果该项目已经存在,则返回布尔值true / false。正如前面所说的那样,是否设置是否缺乏透明性,阅读这些行的程序员必须与文档进行协商才能知道“ add”返回的内容,因为方法的名称并不意味着应该由该方法返回的任何内容,我只是在集合右添加一个新字符串“ Neyney”? 我应该得到什么回报?

The simple answer is that CQS, like any other principles, is not a hard rule that should be applied everywhere, it always comes with a cost of complexity, like seen in the example of the method ‘changeTo’ we had to introduce a whole new class and methods to follow CQS along with violation of Law of Demeter, keeping the method as it is without changes will be simpler to use, shorter to use and more (arguably) readable code when using the method as a simple operation as adding a new item to set wouldn’t take full 3 lines of code to accomplish.

一个简单的答案是,CQS像其他任何原理一样,并不是一个硬性规则,不应应用到任何地方,它总是伴随着复杂性的代价,就像在“ changeTo”方法示例中所看到的那样,我们不得不引入一种全新的方法。遵循CQS的类和方法以及违反Demeter定律的情况,将方法作为简单操作(如添加新方法)使用时,将其保持不变,而无需进行任何更改将更易于使用,使用更短且可读性更高(代码可修改)要设置的项目不会花费全部3行代码来完成。

In short, commands can be seen as the inputs to the object as they alter its state, and queries can be seen as the outputs of the object as they tell us about the state, a method that does both, ties its input and output together which can sometimes lead to harder testability and lack of transparency.

简而言之,命令可以被视为对象改变状态时的输入,查询可以被视为对象告诉我们状态时的输出,这是一种同时将其输入和输出联系在一起的方法有时可能导致更难的可测试性和缺乏透明度。

翻译自: https://medium/swlh/about-command-query-separation-and-object-oriented-design-c5dd4a5e03fb

物化视图和命令查询职责分离

本文标签: 命令视图面向对象职责