Go 語言中的物件導向

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。![](https://raw.githubusercontent.com/studygolang/gctt-images/master/object-orientation/gophermessages.jpg)> 什麼是物件導向呢?這裡我試著分享我對 go 語言如何?物件導向的理解,以及讓它看起來比其他傳統的物件導向語言更加物件導向。在之前的一篇文章中,我探討了用函數來表達一切的想法,而實際上只是用一種安全的方式來表示一組函數,它總是與相同的閉包一起運行,並且可以在相同的狀態下運行。安全地表示在同一個狀態下啟動並執行一組函數是非常有用的,但是如何真正的建立具有多種不同實現的抽象呢?對象本身不支援這一點,每種物件類型都有它自己的函數集(我們稱這組函數為方法)。這被稱為多態,但是當我從傳統的物件導向的概念(關於對象和類型)離開,我發現,在設計軟體時,考慮協議而不是多態性更符合應該關注的內容(稍後會詳細介紹)。## 如何定義一個協議?首先讓我們定義一下什麼是協議,對於我來說協議就是實現預期結果所需的一系列操作。這個概念似乎是晦澀難懂的,讓我舉個例子,一個更容易理解的關於一個抽象概念的栗子,它就是 I/O。假如你想讀取一個檔案,協議將是:* 開啟檔案* 讀取檔案的內容* 關閉檔案為了實現讀取一個檔案所有內容的簡單需求,你需要這三個操作,因此這些操作就構成了你“閱讀檔案”的協議。現在讓我們來看看這個例子,並且一起完成剩餘的實現。如果函數在一門程式設計語言中是一等公民,結構化函數和結構化資料並無區別。我們有一種合成資料的方法,那就是結構體(structs),我們也可以使用這種方法來合成函數,例如:```gotype Reader func(data []byte) (int, error)type Closer func() errortype ReadCloser struct {Read ReaderClose Closer}type Opener func() (*ReadCloser, error)func useFileProtocol(open Opener) {f, _ := open()data := make([]byte, 50)f.Read(data)f.Close()}func main() {useFileProtocol(func() (*ReadCloser, error) {return &ReadCloser{}, nil})}```使用編譯時間安全的方法來表達一個協議是非常困難的(假如這並不是不可能的)。為了說明這個問題,這個例子造成了一個段錯誤。另一個問題是實現這個協議的代碼需要知道協議被顯示地實現,以便正確地初始化結構體(就像繼承的過程一樣),或者把結構體的初始化委託給系統的其他部分,該部分將圍繞如何正確的初始化結構體展開。當你考慮實現多種協議的相同函數時,將會變得更加糟糕。需要一些第三個協議的對象需要一種方法:* 清楚的表達出它所需要的協議。* 確保當它開始與一個實現互動時,沒有任何功能丟失。實現服務的對象需要:* 能夠安全地表示它具有滿足協議的必須功能。* 能夠滿足一個協議,甚至對它不是很清晰的瞭解。不需要兩個對象互動來實現一個特定類型的共同目標,最重要的是它們之間的協議是否匹配。這就是 Go interfaces(介面)的由來。它提供了一個編譯時間安全的方式來表現協議,通過適當的函數來消除初始化結構的所有模板。它會為我們初始化結構,甚至對初始化結構最佳化一次,這在 Go 中被稱為 iface 。類似於 C++ 的 vtable 。它還允許代碼更加的解耦,因為你不需要瞭解定義這個介面 (interface)並且實現這個介面的包。與相同的編譯安全型語言Java、C++比較,在 Go 允許的基礎上,Go更加的靈活。讓我們重新審視之前的帶有介面(interfaces)的檔案協議:```gopackage maintype Reader interface {Read(data []byte) (int, error)}type Closer interface {Close() error}type ReadCloser interface {ReaderCloser}type Opener func() (ReadCloser, error)type File struct {}func (f *File) Read(data []byte)(int, error){return 0, nil}func (f *File) Close() error {return nil}func useFileProtocol(open Opener) {f, _ := open()data := make([]byte, 50)f.Read(data)f.Close()}func main(){useFileProtocol(func() (ReadCloser, error) {return &File{}, nil})}```一個關鍵的不同是, 這段代碼使用了介面(interface),現在是安全的。那個`useFileProtocol`不必擔心調用函數是否為nil,go編譯器將會建立一個結構體,通過一個 `iface`描述符來保持一個指標,該指標具有滿足協議的所有功能。它會按類型<->介面的每一個匹配項執行此操作,就像它被使用的那樣(它第一次初始化使用的那樣)。如果你這樣做,仍然會造成一個段錯誤,如下:```gouseFileProtocol(func() (ReadCloser, error) {var a ReadCloserreturn a, nil})```如果它編譯,就可以確定調用介面的所有函數是安全的。還有 Go 的介面機制,一個對象可以在不知道這些協議的情況下實現多個不同的協議。實現協議是真的有用的嗎?你甚至都不知道它的存在。如果你希望代碼真正具有可擴充性,那麼這是非常有用的。讓我提供一個來自 nash 的真實案例 (nash : 一個 GitHub 開源庫,https://github.com/NeowayLabs/nash)。## 代碼擴充超出其最初的目的當我嘗試為 nash 上的內建函數 exit 編寫測試代碼時,第一次瞭解到 go 的 interfaces 是多麼的強大。主要的問題是,似乎我們必須為每個平台實現不同的測試,因為在某些平台上,退出狀態碼的處理方式不同。我現在不記得所有的細節,但在 plan9 上,退出狀態是一個 string 類型,而不是一個 integer 類型。基本上在一個錯誤上,我想要的是狀態碼,而不僅僅是錯誤,就像在 Cmd.run 上提供的。(檔案 Cmd.run : https://golang.org/pkg/os/exec/#Cmd.Run) 有 ExitError 類型,我可以這樣做: (ExitError 類型: https://golang.org/pkg/os/exec/#ExitError)```goif exiterr, ok := err.(*exec.ExitError); ok {}```至少知道,這是一個通過在我執行的過程中的一些錯誤狀態產生的一些錯誤,但我還是找不到實際的狀態代碼。所以我繼續沉浸在 Go 的原始碼庫中,去尋找如何從 ExitError 中得到錯誤狀態代碼。我的線索是 ProcessState ,ProcessState 是 ExitError 結構體的內部組成(ExitError 是 stuct 類型)。方法 Sys 就說明了:```go// Sys returns system-dependent exit information about// the process. Convert it to the appropriate underlying// type, such as syscall.WaitStatus on Unix, to access its contents.func (p *ProcessState) Sys() interface{} {return p.sys()}```interface{} 什麼也說明不了,但隨著它的線程,我發現一個 posix 的實現:```gofunc (p *ProcessState) sys() interface{} {return p.status}```並且 p.status 是什麼呢,在 posix 中:```gotype WaitStatus uint32```用有趣的方法:```gofunc (w WaitStatus) ExitStatus() int {if !w.Exited() {return -1}return int(w>>shift) & 0xFF}```但是這是針對 posix ,其他平台呢?使用這種先進的檢測技術:```syscall % grep -R ExitStatus ../syscall_nacl.go:func (w WaitStatus) ExitStatus() int { return 0 }./syscall_bsd.go:func (w WaitStatus) ExitStatus() int {./syscall_solaris.go:func (w WaitStatus) ExitStatus() int {./syscall_linux.go:func (w WaitStatus) ExitStatus() int {./syscall_windows.go:func (w WaitStatus) ExitStatus() int { return int(w.ExitCode) }./syscall_plan9.go:func (w Waitmsg) ExitStatus() int { ```看起來像公用協議被足夠多的平台所實現,這對我來說至少是足夠的(windows + linux + plan9 是足夠的。)。現在我們有一個共同的協議所有的平台我們可以這樣做:```go// exitResult is a common interface implemented by// all platforms.type exitResult interface {ExitStatus() int}if exiterr, ok := err.(*exec.ExitError); ok {if status, ok := exiterr.Sys().(exitResult); ok {got := status.ExitStatus()if desc.result != got {t.Fatalf("expected[%d] got[%d]", desc.result, got)}} else {t.Fatal("exit result does not have a ExitStatus method")}}```完整的代碼可以在這裡找到。(https://github.com/NeowayLabs/nash/blob/c0cdacd3633ce7a21714c9c6e1ee76bceecd3f6e/internal/sh/builtin/exit_test.go)Sys() 方法返回了一個抽象的、更加精確的介面,這將是更容易的想出一個新的介面,這將是一個介面的子集,並且能保證編譯時間的安全,而不是通過檢查運行時得到的運行時安全。但即使是一個簡單的方法來定義一個新的 interface 並且不改變原始碼的情況下執行一個安全的運行時檢查,這樣實現 interface 是很簡潔的。在 Java 或 c++ 語言中,我不能想出一個解決方案,包含相同數量的代碼/複雜性,特別因為基於多態性的階層的脆性的。檢查只被允許這種情況,如果原始代碼知道你正在檢查的介面,並且明顯的是繼承自它。為瞭解決我的問題我不得不改變 go 的核心代碼,去瞭解我的 interface ,在 Go 的 interfaces 中,這個不是必需的( yay 階層)。這是非常重要的, 因為它允許開發人員提出簡單的對象, 因為它們不必預測對象將來可能使用的每一種方式, 就像接下來可能會有哪些介面有用一樣。只要你的對象的協議是明確的、有用的,它就可能被重用在幾個你從未想過有可能的方面。你甚至不需要顯式地表達介面定義和使用。## 物件導向到底是什麼呢?這一部分,我將試著去談一談 Go 作為一個了不起的物件導向的語言。所有的早期我所接觸的程式設計語言,像:* Java* C++* Python並且瞭解了繼承,多重繼承,菱形繼承(這是 C++ 中多重繼承中的一個問題,即兩個父類繼承於同一個類。)等。主要的關注點在類型和繼承樹,一個分類的練習。什麼是類型和繼承樹。就像創造一個良好的分類,將是一個良好的物件導向設計。進一步的我開始和那些說物件導向的人討論物件導向並不是關於這個的(儘管所有主流的物件導向語言),這樣的設計並不靈活。我不明白,但引起了我的好奇。理解它的最佳機會是最接近它的核心,所以我去尋找了關於 Alan Kay 的物件導向的資料。(Alan Kay 天才電腦大師阿倫凱,他是 Smalltalk 物件導向編程環境語言的發明人之一,也是物件導向編程思想的創始人之一。)還有更可怕的東西可以從他那裡學到的,但他在 OOPSLA 有過物件導向的主題演講,(演講主題是 The computer revolution has not happened yet 。地址:https://www.youtube.com/watch?v=oKg1hTOQXoY),他討論了一點關於物件導向的起源(在其他事物之中)。他說物件導向之間應該關注什麼是對象,而不是對象本身。他甚至說,面向更多的過程的名稱會更好,因為關注對象似乎已經產生了關注類型和分類,而不是這個對象之間實際存在的東西,對我來說,這是協議。思考對象的重要部分是封裝(不是類型)。他給出了一個很好的例子,那就是細胞,他們有明確的細胞膜,他們允許出去,也允許進入。彼此互動的每一個細胞都對彼此內部運作一無所知,他們不需要知道其他細胞類型,他們只需要實現相同的協議,交易相同的蛋白質等(我不擅長生物學=))。重點是化學反應(過程)而不是細胞類型。所以我們最終封裝和明確的協議作為物件導向應該是什麼,如何開發系統和一個偉大的隱喻,模仿生物機制,而是因為有機生命尺度數量級更好。另一個偉大的隱喻可以從編程示範的未來中提取出來。當談到 ARPANET 和 "星際電腦網路(Intergalactic Computer Network)" 的開始時, 用來表達系統如何真正擴充的隱喻之一是軟體將如何與其他完全陌生的軟體整合 (甚至來自其他星球)。隱喻是偉大的,因為它顯示了良好的協議和形式需要做內容/協議談判,這是自然發生了什麼,也許總有一天會發生什麼,如果我們遇見外星生命(希望我們不要愚蠢地戰鬥到死)。這個比喻甚至為動態語言提供了一些點,但老實說,我對這一點沒有理解,這是足夠好的,現在提出的東西 (我發現很難思考的東西真的適應沒有動態,甚至在 Go 中,你需要一些膠水,通過手動建立的協議整合對象的代碼)。現在這個比喻最重要的一點, 不是最好代表這種系統, 而是要有正確的心態去尋找可能的答案, 從演講中引述:```The most dangerous thought that you can have as a creative person is tothink that you know what you are doing```儘管我試著保持開放的心態, 但當我想到使用上面的隱喻進行編程時, 我找不到繼承的空間。外星人的生命永遠不會融合, 因為他們需要一個可能根本不存在的共同祖先。## 結論上面我給的例子已經顯示了一眼在 Go 中如何做更多,而無需改變任何預先存在的代碼使用 協議(Go的interfaces) 的概念代替類。似乎更容易開發根據開閉原則(開閉原則:open closed principle,https://en.wikipedia.org/wiki/Open/closed_principle),因為我可以輕鬆地擴充其他代碼去做事情,這不是最初打算不用改變它。Go 和 Java 都有 interfaces 的概念,這是似乎具有誤導性的,因為他們唯一的共同點是他們的名字。在 Java 中介面的建立是一個關係,在 Go 中並不是。它只是定義了一個協議,可用於直接整合的對象不知道對方(他們甚至不需要知道明確的介面)。這似乎更加物件導向,比任何到目前為止我知道的(當然,我並不知道太多=))。## 鳴謝特別感謝:* i4k (https://github.com/tiago4orion)* kamilash (https://github.com/kamilash)花時間回顧並指出很多愚蠢的錯誤。

via: https://katcipis.github.io/blog/object-orientation-go/

作者:TIAGO KATCIPIS 譯者:MengYP 校對:polaris1119

本文由 GCTT 原創編譯,Go語言中文網 榮譽推出

本文由 GCTT 原創翻譯,Go語言中文網 首發。也想加入譯者行列,為開源做一些自己的貢獻嗎?歡迎加入 GCTT!
翻譯工作和譯文發表僅用於學習和交流目的,翻譯工作遵照 CC-BY-NC-SA 協議規定,如果我們的工作有侵犯到您的權益,請及時聯絡我們。
歡迎遵照 CC-BY-NC-SA 協議規定 轉載,敬請在本文中標註並保留原文/譯文連結和作者/譯者等資訊。
文章僅代表作者的知識和看法,如有不同觀點,請樓下排隊吐槽

402 次點擊  

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.