命令查詢分離的藝術

banq發表於2017-02-24
函式有副作用。有時候,他們會改變系統的狀態,特別是當你最不希望這麼做時,從而造成各種各樣的意想不到的破壞。在物件導向的程式設計正規化中很難去除所有的副作用。我們需要確保可變狀態得到明確管理,才能保證在我們沒有注意到時不會被狀態拖累。

將副作用管理起來
管理副作用的一個好方法是對命令和查詢分離,並在它們之間建立一個強大的分隔邊界。在良好命令和查詢分離的情況下,當一個命令改變系統狀態時,它就會有副作用。而查詢只是返回一個計算好或系統的觀測狀態的值,並不會有任何改變,因此沒有任何副作用。

例如當你呼叫一個函式getAmount(),你希望它只是返回的金額,而不會同時改變系統內狀態。同樣,當你呼叫setAmount() 時,它會有副作用,你期望它只是改變系統的狀態。但你不會期望setAmount()同時還返回修改後的金額。(單一職責)

命令查詢分離CQS/CQRS的定義:改變狀態的函式不應該返回值,函式返回值不應該改變狀態。

這個術語是由Bertrand Meyer在他的“ 物件導向軟體構造”一書中創造的。

優點
遵循此分離的優點之一是,您可以輕鬆地識別函式是否具有副作用。
int m(); // 查詢(無副作用)
void n(); //命令(有副作用)

看看下面的程式碼,它是命令還是查詢?顯然,它改變了系統的狀態。
User u = UserService.login(username, password);

這是一個login函式呼叫,實現登陸功能職責,為什麼它在登陸時還返回使用者物件?下面這樣查詢使用者函式實現返回使用者物件不是更簡單嗎?

User u = UserService.getUser();

當命令更改狀態出錯時,login()函式是返回錯誤程式碼還是丟擲異常?最好是丟擲異常,這樣能夠保持login()命令返回的還是約定void。

儘管CQS在大多數時候都工作良好,但也有例外:
Element e = Stack<Element>.pop(); // 有態查詢

這裡pop()是一個堆疊彈出函式,彈出不但改變了堆疊集合內部狀態,而且同時返回了彈出的值,這是一個命令和查詢混合在一起的案例,當然可以透過純函式方式來處理這種集合處理。

最後總結一下:
1.命令是返回void,而查詢是返回值。

2.使用異常替代函式直接返回錯誤狀態。

正如馬丁·福勒所說,如果語言本身支援這些概念將是會很好。該語言可以檢測狀態改變方法,或者至少允許程式設計師標記它們。但是我們還沒有看到支援這種標記的語言或IDE。



OO Tricks: The Art of Command Query Separation – F

相關文章