[譯] 類(Class)與數據結構(Data Structures)

類(Class)與數據結構(Data Structures)html

什麼是類?前端

一個類就是一組類似對象的集合的規範。android

對象是什麼?ios

對象是一組對封裝的數據元素進行操做的函數。git

更確切的說,對象是一組對隱含的數據元素進行操做的函數。github

隱含的數據元素是什麼意思?數據庫

對象提供了某些功能,就意味着這個對象包含了一些數據元素;可是這些數據並不能在對象外部直接訪問,在對象外部來看,它們是不可見的。後端

那麼數據不在對象內嗎?數據結構

這是有可能的,可是並無強制規定必須這樣。從用戶的角度來看,對象僅僅是一組函數。這些函數操做的數據必定存在,可是這些數據的位置對用戶是未知的。架構

嗯。好的,我明白了。

很好。那麼什麼是數據結構呢?

數據結構是一組相關性很強的數據元素。

或者,換句話說,數據結構是一組被隱含的函數操做的數據元素。

好的,我懂了。操做數據結構的函數並不被數據結構自己定義,可是它的存在暗示出,該操做函數必定存在。

是的。如今,對於這兩個定義,你注意到了什麼嗎?

它們在某種程度上是相互對立的。

確實是這樣。它們是互補的。它們就像手和手套同樣契合。

對象是操做隱含數據元素的一組函數。 數據結構是被隱含的函數操做的一組數據元素。

哇哦,因此對象並非數據結構。

正確。對象和數據結構是相互對立的。

因此,DTO —— 數據傳輸對象 —— 並非對象?

正確,DTO 是數據結構。

因此數據庫表也並非對象?

正確。數據庫包含了數據結構,而不是對象。

等等。ORM —— 對象關係映射 —— 不是將數據庫表映射爲對象了嗎?

固然不是。數據庫表和對象之間不存在映射關係。數據庫表是數據結構,而不是對象。

那麼 ORM 作了什麼。

它們在數據結構之間傳輸數據。

它們和對象毫無關係嗎?

是的,毫無關係。並無所謂的對象關係映射;由於數據庫表和對象之間並不存在映射關係。

可是我認爲 ORM 爲咱們構建了業務對象。

不是的,ORM 抽象出了咱們的業務對象操做的數據。這些數據被 ORM 加載,存在於數據結構之中。

那麼並非業務對象包含了這些數據結構?

可能包含。也可能不包含。但這都不是 ORM 須要負責的事了。

看起來只是個小小的語義點。

徹底不是。這個區別有重要的意義。

好比說?

好比設計數據庫模式和設計業務對象。業務對象定義了業務行爲的結構。數據庫模式定義了業務數據的結構。這兩個結構是被很是不一樣的條件約束的。業務數據的結構可能並不適用於業務行爲。

嗯,這很讓人迷惑呀。

你能夠這樣來想這個問題,數據庫模式並不會僅僅爲一個應用而調整;它必需要服務於整個企業。因此這些數據的結構是多種不一樣應用需求的折中選擇。

好的,這一點我明白了。

很好。如今考慮每一個單獨的應用。每一個應用的對象模型都描述了這些應用行爲的構成方式。每一個應用都有不一樣的對象模型,這些模型都是爲每一個應用的行爲而量身定作的。

哦,我懂了。因爲數據庫模式是各類應用程序的折中選擇,因此這個模式和任何一個應用的對象模型都能不剛好匹配。

正確!對象和數據結構都被很是不一樣的條件約束。它們不多可以完美地契合。人們習慣稱之爲對象/關係阻抗不匹配。

我聽過這個。但我以前還覺得這種阻抗不匹配被 ORM 解決了。

如今你知道不一樣的答案了。並無什麼阻抗不匹配,由於對象和數據結構是互補的,不是同構的。

你說什麼?

它們是對立的,而不是類似的實體。

對立的?

是的,以一個很是有趣的方式對立。你看,對象和數據結構意味着截然相反的控制結構。

等一下,你說什麼?

想象一組對象類,它們全都能和一個通用接口相符合。例如,表明了兩種尺寸的圖片類都有計算形狀的面積 area 和周長 perimeter 的方法。

爲何全部軟件的示例總會包含圖形呢?

咱們來考慮兩個不一樣的形狀:方形和圓形。咱們都知道,這兩種類的周長和麪積的計算函數對不一樣的隱含數據結構進行操做。咱們也都知道,這些操做被調用的方式是經過動態多態性。

等等。慢一點。你說什麼?

有兩個不一樣的計算面積的方法;一種用來計算方形面積,另外一個則用來計算圓形的。當調用者基於特定類型的對象調用面積函數的時候,是對象決定了調用哪一個函數。咱們稱之爲動態多態性。

好的。是這樣。對象決定了方法如何實現。這是固然的。

如今,咱們將對象換成數據結構。咱們將會使用 Discriminated Unions。

Discriminated Unions 是什麼?

Discriminated Unions,在這個例子中其實就是兩個不一樣的數據結構。一個用於方形,一個用於圓形。圓形數據結構的數據元素包括一箇中心點座標,和一個半徑。同時它也有一個類型代碼,表示它表明圓形。

你是說,就像一個枚舉類型?

是的。方形的數據結構包含了左上角的點,以及邊長。同時它也有鑑別類型的代碼 —— 一個枚舉類型。

嗯是的,兩個數據結構和一個類型代碼。

沒錯。如今考慮面積函數。它須要在內部切換狀態,不是嗎?

嗯,在兩個不一樣的情境下,確實須要。一個用於計算方形面積一個用於計算圓形面積。同時計算周長的函數也須要相似的狀態切換。

沒錯。如今思考一下這兩種場景下的結構。在對象場景下,面積函數的兩種實現是互相獨立的,並在必定程度上是屬於類型的。方形的面積函數屬於方形,而圓形面積的計算屬於圓形。

是的,我知道你的思路了。在數據結構的場景下,面積函數的兩種實現是在同一個函數中,它們不「屬於」任何一個類型。

事情變得愈來愈清晰了。若是你想要爲對象添加三角形類型,你必須更改哪些代碼?

不須要修改任何代碼。你必須新建一個三角形的類。可是我認爲建立實例的方法須要更改。

沒錯。因此當你添加一個新的類型的時候,須要修改的地方很是少。如今,好比你想要新添加一個函數 —— 計算中心點的函數。

那麼如今你必須爲三種類型:圓形,方形和三角形,都加上這個函數。

很是好。因此,添加一個新的函數是比較困難的,你須要修改每一個類。

可是有了數據結構,就不一樣了。爲了添加三角形這個類型,你必須爲修改每一個函數,爲它們都加上三角形這種狀態切換。

是的。新建類型也很困難,你須要修改每一個函數。

可是當你添加新的計算中心的函數時,其餘沒什麼須要修改的。

沒錯。添加新函數很容易。

哇,這和前文所說的是對立的。

確實是。讓咱們來回顧一下:

爲一組類添加新的函數很困難,你須要修改每一個類。 爲一組數據結構添加新的函數很容易,你只須要添加函數,別的不用改。 爲一組類添加新的類型很容易,你只須要新添加一個類。 爲一組數據結構添加新的類型很困難,你須要修改每一個函數。

是的,確實很對立。可是是以一種頗有趣的方式對立起來的。我是說,若是你是要爲一組類型添加新的函數,那我就會想要選擇使用數據結構。可是若是你是想要添加新的類型,那麼你就會想要使用類。

你提出了很棒的意見!可是今天咱們還要思考最後一件事。在另外一個方面,數據結構和類也是相互對立的。和依賴有關。

依賴?

是的,源代碼依賴這個方面。

好吧,我要抓狂了。有什麼區別呢?

首先考慮數據結構的場景下。每一個函數都有一個 switch 語句,它會基於枚舉類型代碼選擇合適的實現。

是的,確實是這樣。但這樣有如何呢?

想象咱們調用了面積函數。調用函數的對象取決於面積函數,而面積函數取決於每一個特定的實現。

如何「取決於」的呢?

想象一下,每一個面積計算方法都在對象自己的函數中實現。因此會有圓形面積,方形面積和三角形面積。

好,因此 switch 語句只調用這些函數。

想象一下這些函數都在不一樣的源文件中。

那麼這些帶有 switch 語句的源文件就須要導入,或者使用,或者包含全部這些源文件。

正確。這就是源代碼依賴。一個源文件依賴於另外一個源文件。那麼這種依賴的方向是什麼呢?

具備 switch 語句的源文件依賴於包含全部實現函數的源文件。

那麼面積函數的調用者又如何呢?

面積函數的調用者依賴於帶有 switch 語句的源文件,而這個文件又依賴於具備全部實現的源文件。

正確。全部源文件依賴都指向調用的方向,從調用者到實現。因此,若是你在這些實現中作了一個錯誤的修改…

好的,我明白你的意思了。任何一個實現中的修改都將會致使具備 switch 語句的源文件被從新編譯,從而致使任何使用了這個 switch 語句的函數 —— 好比咱們的面積計算函數 —— 被從新編譯。

是的。至少對於依賴於源文件的日期來肯定應該編譯哪些模塊的語言系統來講是這樣的。

它們幾乎全都使用靜態類型,是吧?

是的,可是有一些不是。

這須要大量的從新編譯啊。

同時也須要大量的從新部署。

好吧,可是這些缺點在使用類的場景下是否能夠被解決?

是的,由於面積函數的調用者取決於某個接口,同時負責實現的函數也依賴於這個接口。

我懂了。方形類的源文件引入,或者使用,或者包含了形狀這個接口的源文件。

是的。包含了實現的源文件在調用的相反方向有做用。它們是從實現指向調用者的。至少對於靜態類型語言這是確定的。而對於動態類型語言,面積函數的調用者徹底不依賴於任何東西。只在運行時才能找到它的依賴。

沒錯,是這樣。因此若是你修改了其中一個實現…

僅有被修改的文件須要從新編譯或者部署。

這是由於源文件之間的依賴的方向和調用的方向相反。

正確。咱們稱之爲依賴反轉。

好,讓我來看看我是否能總結這部份內容。類和數據結構在至少三個方面互相對立。

  • 類暴露出函數而隱藏數據。數據結構暴露數據可是隱藏函數。
  • 類讓增長類型容易,可是增長方法很困難。數據結構讓增長函數很容易,可是增長類型困難。
  • 數據結構讓調用者須要反覆編譯和部署。類將調用者從須要反覆編譯和部署的部分隔離開了。

你全都說對了。這些是每一個優秀的軟件設計者和架構者須要牢記於心的。

若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索