一個典型的類型系統包括:php
在GO語言中能夠爲任意類型(包括內置類型)添加相應的方法html
上面的例子中咱們定義了一個新類型 Integer ,Integer和int沒有本質區別,只是爲內置的int增長了一個方法Less(),可讓整型像一個普通的類同樣使用了。git
在GO語言中沒有隱藏的this指針github
1) 方法施加的目標顯示傳遞,沒有被隱藏起來golang
2) 方法施加的目標不須要非得是指針,也不用非得叫thisweb
GO語言和C語言同樣,類型都是基於值傳遞的,要想改變變量的值,只能在函數中傳遞指針。面試
1. 類型系統編程
1.1. 值語義和引用語義數組
值語義和引用語義的差異在於賦值app
b = a
b.Modify()
若是b的修改不會影響a的值,那麼此類型屬於值類型。若是會影響a的值,那麼此類型是引用類型。
GO語言中的大多數類型都屬於值語義,包括:
基本類型: byte, int, bool, float32, float64和string等
複合類型: array, struct, pointer等
GO語言中的類型的值語義表現的很是完全。
1.2. 結構體
GO語言放棄了包括繼承在內的大量面向對象特性,只保留了組合這個最基礎的特性。
組合不能算面向對象的特性,由於在C語言這樣的過程式編程語言中,也有結構體,也有組合。組合只是形式複合類型的基礎。
GO語言中結構體的使用方式與C語言並無什麼明顯的不一樣。
2. 初始化
在GO語言中,未進行顯式初始化的變量都會被初始化爲該類型的零值,例如bool類型的零值爲false, int類型的零值爲0, string類型的零值爲空字符串。
結構體有多種初始方法,以下:
確切的說,GO語言也提供了繼承,可是採用了組合的文法,因此咱們將其稱爲匿名組合
4. 可見性
GO語言對關鍵字的增長很是吝嗇,沒有private, protected, public這樣的關鍵字。要使某個符號對其餘包可見,須要將該符號定義爲以大寫字母開頭。
5. 接口
GO語言的接口並非其餘語言中所提供的接口概念。JAVA如今的接口是侵入式接口, GO的接口時非侵入式的。
在GO語言中,一個類只要實現了接口要求的全部函數,咱們就說這個類實現了該接口。
在GO語言中,接口賦值在GO語言中分爲以下兩種狀況:
1. 將對象實例賦值給接口
2. 將一個接口賦值給另外一個接口
//struct //Date:2014-4-1 09:57:37 package main import ( "fmt" "strings" ) func StructTest01Base() { //structTest0101() //structTest0102() structTest0103() } //定義一個struct type Student struct { id int name string address string age int } func structTest0101() { //使用new建立一個Student對象,結果爲指針類型 //var s *Student = new(Student) s := &Student{} //var s *Student = new(Student) //var s *Student = new(Student) s.id = 101 s.name = "Mikle" s.address = "紅旗南路" s.age = 18 fmt.Printf("id:%d\n", s.id) fmt.Printf("name:%s\n", s.name) fmt.Printf("address:%s\n", s.address) fmt.Printf("age:%d\n", s.age) fmt.Println(s) } func main(){ structTest0101() } //建立Student的其它方式 func structTest0102() { //使用&T{...}建立struct,結果爲指針類型 var s1 *Student = &Student{102, "John", "Nanjing Road", 19} fmt.Println(s1) fmt.Println("modifyStudentByPointer...") modifyStudentByPointer(s1) fmt.Println(s1) //使用T{...}建立struct,結果爲value類型 fmt.Println("-------------") var s2 Student = Student{103, "Smith", "Heping Road", 20} fmt.Println(s2) fmt.Println("modifyStudent...") modifyStudent(s2) fmt.Println(s2) //建立並初始化一個struct時,通常使用【上述】兩種方式 //其它方式 var s3 *Student = &Student{id: 104, name: "Lancy"} fmt.Printf("s3:%d,%s,%s,%d\n", s3.id, s3.name, s3.address, s3.age) } //struct對象屬於值類型,所以須要經過函數修改其原始值的時候必須使用指針 func modifyStudent(s Student) { s.name = s.name + "-modify" } func modifyStudentByPointer(s *Student) { s.name = s.name + "-modify" } type Person struct { firstName string lastName string } //使用 *Person做爲參數的函數 func upPerson(p *Person) { p.firstName = strings.ToUpper(p.firstName) p.lastName = strings.ToUpper(p.lastName) } //調用上述方法的三種方式 func structTest0103() { //1- struct as a value type: var p1 Person p1.firstName = "Will" p1.lastName = "Smith" upPerson(&p1) fmt.Println(p1) //2—struct as a pointer: var p2 = new(Person) p2.firstName = "Will" p2.lastName = "Smith" (*p2).lastName = "Smith" //this is also valid upPerson(p2) fmt.Println(p2) //3—struct as a literal: var p3 = &Person{"Will", "Smith"} upPerson(p3) fmt.Println(p3) }
目錄
一. 抽象和封裝
二. 繼承(Composition)
1. has-a
2. is-a(Pseudo)----Embedding
三. Interface
尾聲
其實有個問題Is Go An Object Oriented Language?
, 隨便谷歌了一下, 你就發現討論這個的文章有不少:
1. reddit
2. google group
那麼問題來了
- Golang是OOP嗎?
- 使用Golang如何實現OOP?
我入門教程基本就是A Tour Of Go
以及Go Web 編程
. 因爲以前是寫C++, 可是說到Go面向對象編程, 老是感受怪怪的, 總感受缺乏點什麼. 我搜集了一些資料和例子, 加上個人一些理解, 整理出這樣一篇文章.
抽象和封裝就放在一塊說了. 這個其實挺簡單. 看一個例子就好了.
type rect struct { width int height int } func (r *rect) area() int { return r.width * r.height } func main() { r := rect{width: 10, height: 5} fmt.Println("area: ", r.area()) }
要說明的幾個地方:
一、Golang中的struct
和其餘語言的class
是同樣的.
二、可見性. 這個遵循Go語法的大小寫的特性
三、上面例子中, 稱*rect
爲receiver
. 關於receiver
能夠有兩種方式的寫法:
func (r *rect) area() int { return r.width * r.height } func (r rect) area() int { return r.width * r.height }
這其中有什麼區別和聯繫呢? 關於詳細解釋請查看astaxie的解釋, 寫的很是清晰.
簡單來講, Receiver能夠是值傳遞, 仍是能夠是指針, 二者的差異在於, 指針做爲Receiver會對實例對象的內容發生操做,而普通類型做爲Receiver僅僅是以副本做爲操做對象,並不對原實例對象發生操做。
四、當Receiver
爲*rect
指針的時候, 使用的是r.width
, 而不是(*r).width
, 是因爲Go自動幫我轉了,兩種方式都是正確的.
五、任何類型均可以聲明成新的類型, 由於任何類型均可以有方法.
type Interger int func (i Interger) Add(interger Interger) Interger { return i + interger }
六、雖然Interger是從int聲明而來, 可是這樣用是錯誤的.
var i Interger = 1 var a int = b //cannot use i (type Interger) as type int in assignment
這是由於Go中沒有隱式轉換
(寫C++的同窗都會特別討厭這個, 由於編譯器揹着咱們乾的事情太多了). Golang中類型之間的相互賦值都必須顯式聲明
.
上面的例子改爲下面的方式就能夠了.
var i Interger = 1 var a int = int(b)
說道繼承,其實在Golang中是沒有繼承(Extend)這個概念. 由於Golang捨棄掉了像C++, Java的這種傳統的、類型驅動的子類。
Go Effictive says:
Go does not provide the typical, type-driven notion of subclassing, but it does have the ability to 「borrow」 pieces of an implementation by embedding types within a struct or interface.
換句話說, Golang中沒有繼承, 只有Composition
.
Golang中的Compostion
有兩種形式, 匿名組合(Pseudo is-a)
和非匿名組合(has-a)
注: 若是不瞭解OOP的is-a
和has-a
關係的話, 請自行google.
package main import ( "fmt" ) type Human struct { name string age int phone string } type Student struct { h Human //非匿名字段 school string } func (h *Human) SayHi() { fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) } func (s *Student) SayHi() { fmt.Printf("Hi student, I am %s you can call me on %s", s.h.name, s.h.phone) } func main() { mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"} fmt.Println(mark.h.name, mark.h.age, mark.h.phone, mark.school) mark.h.SayHi() mark.SayHi() }
Output
Mark 25 222-222-YYYY MIT
Hi, I am Mark you can call me on 222-222-YYYY
Hi student, I am Mark you can call me on 222-222-YYYY
這種組合方式, 其實對於瞭解傳統OOP的話, 很好理解, 就是把一個struct
做爲另外一個struct
的字段.
從上面例子能夠, Human徹底做爲Student的一個字段使用. 因此也就談不上繼承的相關問題了.咱們也不去重點討論.
type Human struct { name string age int phone string } type Student struct { Human //匿名字段 school string } func (h *Human) SayHi() { fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) } func main() { mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"} fmt.Println(mark.name, mark.age, mark.phone, mark.school) mark.SayHi() }
Output
Mark 25 222-222-YYYY MIT
Hi, I am Mark you can call me on 222-222-YYYY
一、字段
如今Student
訪問Human
的字符, 就能夠直接訪問了, 感受就是在訪問本身的屬性同樣. 這樣就實現了OOP的繼承.
fmt.Println("Student age:", mark.age) //輸出: Student age: 25
可是, 咱們也能夠間接訪問:
fmt.Println("Student age:", mark.Human.age) //輸出: Student age: 25
這有個問題, 若是在Student
也有個字段name
, 那麼當使用mark.name
會以Student
的name
爲準.
fmt.Println("Student name:", mark.name) //輸出:Student Name: student name
二、方法
Student
也繼承了Human
的SayHi()
方法
mark.SayHi() // 輸出: Hi, I am Mark you can call me on 222-222-YYYY
固然, 咱們也能夠重寫SayHi()
方法:
type Human struct { name string age int phone string } type Student struct { Human //匿名字段 school string name string } func (h *Human) SayHi() { fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) } func (h *Student) SayHi() { fmt.Println("Student Sayhi") } func main() { mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT", "student name"} mark.SayHi() }
Output
Student Sayhi
三、爲何稱其爲Pseudo is-a
呢?
由於匿名組合
不提供多態
的特性. 以下面的代碼:
package main type A struct{ } type B struct { A //B is-a A } func save(A) { //do something } func main() { b := new(B) save(*b); }
Output
cannot use *b (type B) as type A in argument to save
還有一個面試題的例子(說明go的匿名組合只有重寫, 沒有重載)
type People struct{} func (p *People) ShowA() { fmt.Println("showA") p.ShowB() } func (p *People) ShowB() { fmt.Println("showB") } type Teacher struct { People } func (t *Teacher) ShowB() { fmt.Println("teacher showB") } func main() { t := Teacher{} t.ShowA() }
輸出結果是什麼呢?
Output
ShowA
ShowB
Effective Go Says:
There's an important way in which embedding differs from subclassing. When we embed a type, the methods of that type become methods of the outer type, but when they are invoked the receiver of the method is the inner type, not the outer one
也就是說, Teacher
因爲組合了People
, 因此Teacher
也有了ShowA()
方法, 可是在ShowA()
方法裏執行到ShowB
時, 這個時候的receiver
是*People
而不是*Teacher
, 主要緣由仍是由於embedding
是一個Pseudo is-a
, 沒有多態的功能.
四、 "多繼承"的問題(go沒有多重繼承, 必須顯示引用)
package main import "fmt" type School struct { address string } func (s *School) Address() { fmt.Println("School Address:", s.address) } type Home struct { address string } func (h *Home) Address() { fmt.Println("Home Address:", h.address) } type Student struct { School Home name string } func main() { mark := Student{School{"aaa"}, Home{"bbbb"}, "cccc"} fmt.Println(mark) mark.Address() fmt.Println(mark.address) mark.Home.Address() fmt.Println(mark.Home.address) }
輸出結果:
30: ambiguous selector mark.Address
31: ambiguous selector mark.address
由此能夠看出, Golang中不論是方法仍是屬性都不存在相似C++那樣的多繼承的問題. 要訪問Embedding
相關的屬性和方法, 須要在加那個相應的匿名字段
, 如:
mark.Home.Address()
五、Embedding value
和 Embedding pointer
的區別
package main import ( "fmt" ) type Person struct { name string } type Student struct { *Person age int } type Teacher struct { Person age int } func main() { s := Student{&Person{"student"}, 10} t := Teacher{Person{"teacher"}, 40} fmt.Println(s, s.name) fmt.Println(t, t.name) }
Output
{0x1040c108 10} student
{{teacher} 40} teacher
I. 二者對於結果來講, 沒有啥區別, 只是對傳參的時候有影響
II. Embedding value
是比較常規的寫法
III. Embedding pointer
比較有優點一點, 不須要關注指針是什麼時間被初始化的.
Golang中Composite
不提供多態的功能, 那是否Golang
不提供多態呢? 答案確定是否認. Golang依靠Interface
實現多態的功能.
下面是我工程裏面一段代碼的簡化:
package main import ( "fmt" ) type Check interface { CheckOss() } type CheckAudio struct { //something } func (c *CheckAudio) CheckOss() { fmt.Println("CheckAudio do CheckOss") } func main() { checkAudio := CheckAudio{} var i Check i = &checkAudio //想一下這裏爲啥須要&, 這一點是不少書裏面提到的, //即指針才具備指針的方法 //而反過來是不成立的, 非指針不會具備指針的方法 i.CheckOss() }
一、Interface 如何Composite
?
type Reader interface { Read(p []byte) (n int, err error) } type Writer interface { Write(p []byte) (n int, err error) } type ReadWriter interface { Reader Writer }
其實很簡單, 就是把Reader
, Writer
嵌入到ReadWriter
中, 這樣ReadWriter
就擁有了Reader
和Writer
的方法.
至此, 基本說完了Golang的面向對象. 有哪裏我理解的不對的地方, 請給我留言.
參考資料
1. Effective Go: Embedding
2. Go面試題
3. Is Go An Object Oriented Language?
4. go web編程
5. object-oriented-programming-in-go