http://www.cnblogs.com/jasonxuli/p/6836399.html
傳統 OOP 概念
OOP(面向對象編程)是對真實世界的一種抽象思維方式,可以在更高的層次上對所涉及到的實體和實體之間的關系進行更好的管理。
流傳很廣的OOP的三要素是:封裝、繼承、多態。
對象:可以看做是一些特征的集合,這些特征主要由 屬性 和 方法 來體現。
封裝:划定了對象的邊界,也就是定義了對象。
繼承:表明了子對象和父對象之間的關系,子對象是對父對象的擴展,實際上,子對象“是”父對象。相當於說“碼農是人”。從特征的集合這個意義上說,子對象包含父對象,父對象有的公共特征,子對象全都有。
多態:根據繼承的含義,子對象在特性上全包圍了父對象,因此,在需要父對象的時候,子對象可以替代父對象。
傳統的 OOP 語言,例如 Java,C++,C#,對OOP 的實現也各不相同。以 Java 為例: Java 支持 extends,也支持 interface。這是兩種不同的抽象方式。
extends 就是繼承,A extends B,表明 A 是 B 的一種,是概念上的抽象關系。
Class Human { name:string age:int function eat(){} function speak(){} } Class Man extends Human { function fish(){} function drink(){} }
Golang 的 OOP
回到 Golang。Golang 並沒有 extends,它類似的方式是 Embedding。這種方式並不能實現 is-a 這種定義上的抽象關系,因此 Golang 並沒有傳統意義上的多態。
注意下面代碼中的綠色粗體注釋,把 Student 當做 Human 會報錯。
package main import "fmt" func main(){ var h Human s := Student{Grade: 1, Major: "English", Human: Human{Name: "Jason", Age: 12, Being: Being{IsLive: true}}} fmt.Println("student:", s) fmt.Println("student:", s.Name, ", isLive:", s.IsLive, ", age:", s.Age, ", grade:", s.Grade, ", major:", s.Major) //h = s // cannot use s (type Student) as type Human in assignment fmt.Println(h) //Heal(s) // cannot use s (type Student) as type Being in argument to Heal Heal(s.Human.Being) // true s.Drink() s.Eat() } type Car struct { Color string SeatCount int } type Being struct { IsLive bool } type Human struct { Being Name string Age int } func (h Human) Eat(){ fmt.Println("human eating...") h.Drink() } func (h Human) Drink(){ fmt.Println("human drinking...") } func (h Human) Move(){ fmt.Println("human moving...") } type Student struct { Human Grade int Major string } func (s Student) Drink(){ fmt.Println("student drinking...") } type Teacher struct { Human School string Major string Grade int Salary int } func (s Teacher) Drink(){ fmt.Println("teacher drinking...") } type IEat interface { Eat() } type IMove interface { Move() } type IDrink interface { Drink() } func Heal(b Being){ fmt.Println(b.IsLive) }
輸出結果:
student: {{{true} Jason 12} 1 English} student: Jason , isLive: true , age: 12 , grade: 1 , major: English {{false} 0} true student drinking... human eating... human drinking...
這里有一點需要注意,Student 實現了 Drink 方法,覆蓋了 Human 的 Drink,但是沒有實現 Eat 方法。因此,Student 在調用 Eat 方法時,調用的是 Human 的 Eat();而 Human 的 Eat() 調用了 Human 的 Drink(),於是我們看到結果中輸出的是 human drinking... 。這既不同於 Java 類語言的行為,也不同於 prototype 鏈式繼承的行為,Golang 叫做 Embedding,這像是一種寄生關系:Human 寄生在 Student 中,但仍保持一定程度的獨立。
Golang 的接口
我們從接口產生的原因來考慮。
代碼處理的是各種數據。對於強類型語言來說,非常希望一批數據都是單一類型的,這樣它們的行為完全一致。但世界是復雜的,很多時候數據可能包含不同的類型,卻有一個或多個共同點。這些共同點就是抽象的基礎。單一繼承關系解決了 is-a 也就是定義問題,因此可以把子類當做父類來對待。但對於父類不同但又具有某些共同行為的數據,單一繼承就不能解決了。單一繼承構造的是樹狀結構,而現實世界中更常見的是網狀結構。
於是有了接口。接口是在某一個方面的抽象,也可以看做具有某些相同行為的事物的標簽。
但不同於繼承,接口是松散的結構,它不和定義綁定。從這一點上來說,Duck Type 相比傳統的 extends 是更加松耦合的方式,可以同時從多個維度對數據進行抽象,找出它們的共同點,使用同一套邏輯來處理。
Java 中的接口方式是先聲明后實現的強制模式,比如,你要告訴大家你會英語,並且要會聽說讀寫,你才具有英語這項技能。
interface IEnglishSpeaker {
ListenEnglish()
ReadEnglish()
SpeakEnglish()
WriteEnglish()
}
Golang 不同,你不需要聲明你會英語,只要你會聽說讀寫了,你就會英語了。也就是實現決定了概念:如果一個人在學校(有School、Grade、Class 這些屬性),還會學習(有Study()方法),那么這個人就是個學生。
Duck Type 更符合人類對現實世界的認知過程:我們總是通過認識不同的個體來進行總結歸納,然后抽象出概念和定義。這基本上就是在軟件開發的前期工作,抽象建模。
相比較而言, Java 的方式是先定義了關系(接口),然后去實現,這更像是從上帝視角先規划概念產生定義,然后進行造物。
因為 interface 和 object 之間的松耦合,Golang 有 type assertion 這樣的方式來判斷一個接口是不是某個類型:
value, b := interface.(Type),value 是 Type 的默認實例;b 是 bool 類型,表明斷言是否成立。
// 接上面的例子 v1, b := interface{}(s).(Car) fmt.Println(v1, b) v2, b := interface{}(s).(Being) fmt.Println(v2, b) v3, b := interface{}(s).(Human) fmt.Println(v3, b) v4, b := interface{}(s).(Student) fmt.Println(v4, b) v5, b := interface{}(s).(IDrink) fmt.Println(v5, b) v6, b := interface{}(s).(IEat) fmt.Println(v6, b) v7, b := interface{}(s).(IMove) fmt.Println(v7, b) v8, b := interface{}(s).(int) fmt.Println(v8, b)
輸出結果:
{ 0} false {false} false {{false} 0} false {{{true} Jason 12} 1 English} true {{{true} Jason 12} 1 English} true {{{true} Jason 12} 1 English} true <nil> false 0 false
上面的代碼中,使用空接口 interface{} 對 s 進行了類型轉換,因為 s 是 struct,不是 interface,而類型斷言表達式要求點號左邊必須為接口。
常用的方式應該是類似泛型的使用方式:
s1 := Student{Grade: 1, Major: "English", Human: Human{Name: "Jason", Age: 12, Being: Being{IsLive: true}}} s2 := Student{Grade: 1, Major: "English", Human: Human{Name: "Tom", Age: 13, Being: Being{IsLive: true}}} s3 := Student{Grade: 1, Major: "English", Human: Human{Name: "Mike", Age: 14, Being: Being{IsLive: true}}} t1 := Teacher{Grade: 1, Major: "English", Salary: 2000, Human: Human{Name: "Michael", Age: 34, Being: Being{IsLive: true}}} t2 := Teacher{Grade: 1, Major: "English", Salary: 3000, Human: Human{Name: "Tony", Age: 31, Being: Being{IsLive: true}}} t3 := Teacher{Grade: 1, Major: "English", Salary: 4000, Human: Human{Name: "Ivy", Age: 40, Being: Being{IsLive: true}}} drinkers := []IDrink{s1, s2, s3, t1, t2, t3} for _, v := range drinkers { switch t := v.(type) { case Student: fmt.Println(t.Name, "is a Student, he/she needs more homework.") case Teacher: fmt.Println(t.Name, "is a Teacher, he/she needs more jobs.") default: fmt.Println("Invalid Human being:", t) } }
輸出結果:
Jason is a Student, he/she needs more homework. Tom is a Student, he/she needs more homework. Mike is a Student, he/she needs more homework. Michael is a Teacher, he/she needs more jobs. Tony is a Teacher, he/she needs more jobs. Ivy is a Teacher, he/she needs more jobs.
這段代碼中使用了 Type Switch,這種 switch 判斷的目標是類型。
Golang:接口為重
了解了 Golang 的 OOP 相關的基本知識后,難免會有疑問,為什么 Golang 要用這種“非主流”的方式呢?
Java 之父 James Gosling 在某次會議上有過這樣一次問答:
I once attended a Java user group meeting where James Gosling (Java’s inventor) was the featured speaker.
During the memorable Q&A session, someone asked him: “If you could do Java over again, what would you change?”
“I’d leave out classes,” he replied. After the laughter died down, he explained that the real problem wasn’t classes per se, but rather implementation inheritance (the extends relationship). Interface inheritance (the implements relationship) is preferable. You should avoid implementation inheritance whenever possible.
大意是:
問:如果你重新做 Java,有什么是你想改變的?
答:我會把類(class)丟掉。真正的問題不在於類本身,而在於基於實現的繼承(the extends relationship)。基於接口的繼承(the implements relationship)是更好的選擇,你應該在任何可能的時候避免使用實現繼承。
我的理解是:實現之間應該少用繼承式的強關聯,多用接口這種弱關聯。接口已經可以在很多方面替代繼承的作用,比如多態和泛型。而且接口的關系松散、隨意,可以有更高的自由度、更多的抽象角度。
以繼承為特點的 OOP 只是編程世界的一種抽象方式,在 Golang 的世界里沒有繼承,只有組合和接口,這看起來更符合 Gosling 的設想。借用那位老人的話:黑貓白貓,捉住老鼠就是好貓。讓我來繼續探索吧。
注:剛剛學習 Golang 不久,后面可能會發現也許某些理解是錯誤的。隨時修正。