Common Lisp物件系統是現存最好的物件系統? - mendhekar

banq發表於2021-06-30

軟體中一個常見的設計/架構/分析原則是結構/行為二分法。在這裡,作為系統的設計者,我們確定結構部分,然後確定每個結構部分在系統中的行為方式。
物件導向的設計中,這通常可以幫助我們識別類以及它們如何相互關聯(結構)和方法(行為)。傳統的物件導向乍一看似乎很自然的步驟:它將相關的結構和行為分組到一個物件中。這就是我們向學生教授物件導向程式設計的方式。
將結構和行為結合起來的傳統動機有兩個:
  1. 首先,現實世界中的物件的行為是這樣的。物理物件具有定義其行為的結構。
  2. 第二個是一個單一的機制:繼承,允許重用既結構和行為,這絕對是方便,然後,這種重用成為在軟體設計中採用 OO 的主要驅動力。

您可以使用預先編寫的軟體,並根據您的需要逐步推匯出新的功能。這是一個美好的承諾,隨著軟體的複雜性繼續呈指數級增長,它導致了 OO 的快速採用。
 

傳統OO複雜度增加
在這種世界觀中,結構和行為之間的分離有些丟失。兩者在臀部連線:
方法現在有一個它們從屬的“主要”物件。在傳統的物件導向中,這通常在幾乎每個實現中都被視為合乎邏輯的結論。物件攜帶一個物理連結到物件的方法表(通常透過某種指標),以便編譯器可以實現快速分派(稍後會詳細介紹)。

這種合併結構和行為的設計選擇的問題在於,抽象資訊系統設計的世界與物理世界非常不同。我們的物件的行為取決於它們與之互動的其他物件。當按鈕出現在語音介面和螢幕上時,它必須“看起來”並且可能表現不同。
傳統的物件導向透過其他機制解決了這個問題,通常需要一些技巧。例如,一個簡單的解決方法是建立一個Button類,該類包含一個抽象渲染方法,然後是ScreenButton和VoiceButton 的兩個子類。通常,渲染方法需要不同的渲染上下文,具體取決於它是ScreenRenderingContext還是VoiceRenderingContext。所以現在你需要一個抽象的RenderingContext以便抽象渲染方法的型別,用在ScreenButton和VoiceButton 中作為各個Render渲染方法的超型別。
你在想:這有什麼問題?這是非常自然的 OO 設計。
但我只是從一個結構(按鈕)和兩個行為變成了六個結構 + 行為。在一個小例子中,複雜性增加了 3 倍。在這個簡單的版本中,我們甚至沒有考慮不同的螢幕尺寸和響應能力以及你有什麼。
這將我們帶到下一個物件導向程式設計。幾乎每個現代 OO 系統都是一個巨大的類集合,通常是這樣設計的,可能包含大量不必要的物件和方法,必須編寫這些物件和方法來適應這種限制。
 

Dispatch問題
所有傳統的物件導向程式設計都歸結為一個基本操作:將方法的名稱與要執行的特定程式碼段匹配。程式語言專家將其稱為“Dispatch分派”,這是一種相當常見的程式設計模式,稱為“分派表”。
因此,當遇到複雜行為時,在傳統 OO 中圍繞它進行設計的唯一方法是將複雜行為分解為一​​系列“單一分派”。繼承、匹配型別等的額外約束然後強制引入“中間”單一分派,這通常只是這種設計選擇的技巧。這就是為什麼我們最終得到來自一種結構和兩種行為的 6 個單分派表。
當這種結構和行為的合併變得過於複雜時,傳統的物件導向幾乎放棄並定義了一個新的野獸:介面,它完全放棄了結構來指定行為子集的要求。這是因為在不影響程式碼複雜性目標的情況下真正設計所有這些單個分派是不可能的,因此您必須在某些方面削弱型別約束。
 
實際上,傳統 OO 的每一個特性都可以追溯到這種將結構和行為混為一談的設計選擇,最終形成單一Dispatch分派。在像 Java 這樣的單一正規化語言中,更糟糕的是,你一直被迫以這種特殊的“單一Dispatch分派”方式思考。在 Java 的其他限制中,我發現這是最糟糕的。它極大地限制瞭解決方案設計空間,並引入了比可能需要的更多的複雜性。這也是 ORM 如此糟糕的主要原因之一。不可能以適合所有人的方式將單分派對映到多物件行為。
好的,談論傳統 OO 的問題就夠了。解決辦法是什麼?
 

結構與行為分離
CLOS(Common Lisp Object System的簡稱)對物件導向世界的最大貢獻是它徹底拒絕將結構和行為混為一談。我敢肯定這最後一句話讓你完全困惑/震驚/茫然。你的整個世界一直都是攜帶著自己行為的物件。分離怎麼可能呢?
CLOS 例項定義無行為內在的物件。行為不從屬於任何主要物件,並以通用函式的形式獨立存在。
 

泛型函式
泛型函式是給以引數型別為基礎的行為集合的名稱。因此,例如,我可以在與 ( Button , ScreenRenderContext ) 和 ( Button , VoiceRenderContext )一起使用的Render通用函式上定義方法。當使用引數呼叫時,泛型函式會在兩個引數上分派。即,它查詢與引數關聯的型別最特定的方法,並呼叫該方法。
請注意,我不再需要ScreenButton和VoiceButton,也不需要定義Button是具有未定義渲染方法的抽象類。其實CLOS並沒有抽象類的概念。此外,由於這種結構分離,您可以輕鬆擴充套件泛型函式以在預定義類/內建型別上排程,而無需任何技巧。
CLOS使用方法組合,它還有一些其他的微妙之處(例如方法之前、之後和周圍),使您能夠真正靈活地將行為應用於物件。
然而,所有這些方法組合機制的重點在於,您不僅實現了結構和行為的分離,而且還保留了繼承的好處!您可以在必要時呼叫先前定義的方法,而無需重寫它們。

詳細點選標題

banq:在DDD與OO重要區別中我認為:上下文比抽象更重要,對於上面案例button類,有兩個不同上下文:ScreenRenderingContext還是VoiceRenderingContext,這裡應該以上下文為切分依據,將Button從屬於這兩個不同的上下文,而不是將這兩個上下文從屬於Button這個抽象類。


 

相關文章