最近收到讀者反饋审丘,《架構整潔之道》第 25 章“層次與邊界”中,圖 25.3 和解釋這張圖的一段文字的描述讓人很費解。
如果我們進一步查看 GameRules 內(nèi)部勘天,就會發(fā)現(xiàn) GameRules 組件的代碼中使用的 Boundary 多態(tài)接口是由 Language 組件來實現(xiàn)的;同時還會發(fā)現(xiàn) Language 組件使用的 Boundary 多態(tài)接口由 GameRules 代碼實現(xiàn)。
讀者的疑惑是 GameRules 和 Language 分別定義接口讓對方實現(xiàn)脯丝,那么這兩個組件不就形成雙向依賴了嗎商膊?
我們僅憑直覺就知道雙向依賴肯定是錯的,那么這句話到底該作何解釋巾钉?為了排除翻譯出錯的可能性翘狱,我仔細比照了原文,確定了譯文并不損失原意砰苍。
If we were to look inside GameRules, we would find polymorphic Boundary interfaces used by the code inside GameRules and implemented by the code inside the Language component. We would also find polymorphic Boundary interfaces used by Language and implemented by code inside GameRules.
我回頭翻閱了當時技術審校的文章潦匈,遺憾地發(fā)現(xiàn)自己確實疏漏了對此處的解釋,所以我重新翻閱了書中和邊界(Boundary)相關的章節(jié)赚导,比如第 22 章的“整潔架構”和第 24 章的“不完全邊界”茬缩,給出一個牽強的解釋:我認為 Bob 大叔在這里提到的 Boundary 多態(tài)接口(polymorphic Boundary interfaces)指的是系統(tǒng)在構建完全邊界時需要的 inputBoundary 和 outputBoundary 的。但是不管如何吼旧,也不可能出現(xiàn)他說的低級別的組件(此處的 Language)定義接口讓高級別的組件(GameRules)去實現(xiàn)的情況凰锡。
為了解決讀者心中的疑惑,我寫了封郵件給 Bob 大叔圈暗。這封郵件的內(nèi)容重點在詢問 GameRules 為什么會有對 Language 的依賴掂为。Bob 大叔的回復得很迅速,內(nèi)容如下:
The secret here is that the polymorphic interfaces used by Language and implemented by Game Rules are contained within Game Rules. This keeps the arrows pointing in the same direction.
他強調(diào) Language 使用的多態(tài)接口其實是定義在 GameRules 當中的员串,所以依賴方向依然是從 Language 指向 GameRules 的勇哗。看到這樣的回復寸齐,我還是有些迷惑欲诺,不過認真思考了一段時間,我大概知道這里的誤解到底是什么了渺鹦。
其實答案就隱藏在第 22 章“整潔架構”里圖22.2 一個基于 Web 的扰法、使用數(shù)據(jù)庫的常見 Java 程序。
結合這張圖毅厚,我們不難總結出 Language和 GameRules 的依賴關系塞颁,邊界和高層次的接口定義,這里面最需要澄清的點就是“使用”并不意味著“定義”吸耿,而只是引用祠锣。為了確保我的理解正確,我引用了原文并逐行提出我的問題珍语,然后畫了一些圖锤岸,發(fā)送一封確認郵件。Bob 大叔很仔細地回復了我的問題板乙。我把郵件原文列在底下是偷。其中拳氢,Q 代表我的問題,Answer 是 Bob 大叔的回答蛋铆。
"The diagram in Figure 25.3 has gotten a little complicated, but should contain no surprises. The dashed outlines indicate abstract components that define an API that is implemented by the components above or below them. For example, the Language API is implemented by English and Spanish."
Q1: Language components define interfaces that should be implemented by its derivates like English and Spanish that are below the Language component. So why you emphasise ABOVE them?
Answer: In Figure 25.3 you’ll see SMS and Console above Text Delivery. Text Delivery does not actually exist as an independent module. It is an abstraction, or a set of conventions, that both SMS and Console adhere to. English and Spanish appear below Language. Again, Language does not actually exist as a module. English and Spanish simply adhere to the conventions that we call Language.
"GameRules communicates with Language through an API that GameRules defines and Language implements. Language communicates with TextDelivery using an API that Language defines but TextDelivery implements. The API is defined and owned by the user, rather than by the implementer."
Q2: GameRules defines API (interface) that Language component implements, does it mean GameRules and Language are separated components?
Answer: Not quite. English and Spanish are separate from Game Rules and both implement the API defined in Game Rules. The Language abstraction is draw there to denote that English and Spanish follow the conventions that Game Rule and Text Delivery require.
"If we were to look inside GameRules, we would find polymorphic Boundary interfaces used by the code inside GameRules and implemented by the code inside the Language component. We would also find polymorphic Boundary interfaces used by Language and implemented by code inside GameRules."
Q3: In Chapter 22, there is figure 22.2 a typical scenario for a web-based Java system utilising a database.
Here, I mark connections to explain my understanding for "used by" and "implemented by". So "used by" is not meaning "defined by", right?
Answer: Right.
If my understanding is correct, how about the diagram I draw as follow? does it explain the secret you mentioned above?
"If we were to look inside of Language, we would find the same thing: Polymorphic Boundary interfaces implemented by the code inside TextDelivery, and polymorphic Boundary interfaces used by TextDelivery and implemented by Language."
"In each case, the API defined by those Boundary interfaces is owned by the upstream component.”
Q4: In GameRules and Language components context, upstream component means the GameRules? In Language and TextDelivery components context, upstream means the Language?
Answer: Yes. Upstream means “higher level".
當我把郵件完整地轉發(fā)給讀者后馋评,他表示“我看了 Bob 大叔的回答,感覺對依賴反轉的認識又深了一層刺啦。不留特,應該說看了你的回答(商業(yè)互吹)。抽象出的接口玛瘸,我從來沒想過接口的歸屬問題蜕青,這么看來就合理了『ǎ”
小結
以后我們在設計組件時一定要關心邊界和接口定義的歸屬右核。它代表著依賴反轉原則在更大的架構層面上的運用。
于 2019-10-27