轉自:https://blog.csdn.net/huwh_/article/details/53710495
- 面向對象編程:
把一組數據結構和處理它們的方法組成對象(object),把相同行爲的對象歸納爲類(class),通過類的封裝(encapsulation)隱藏內部細節,通過繼承(inheritance)實現類的特化(specialization)[方法的重寫,子類不同於父類的特性]/泛化(generalization)[共性,子類都擁有父類的特性],通過多態(polymorphism)實現基於對象類型的動態分派(dynamic dispatch)。
- 面對對象思想:
面向對象思想是對現實世界事物的抽象,系統中一切事物皆爲對象;對象是屬性及其操作的封裝體;對象可按其性質劃分爲類,對象成爲類的實例;實例關係和繼承關係是對象之間的靜態關係;消息傳遞是對象之間動態聯繫的唯一形式,也是計算的唯一形式;方法是消息的序列。
(一)類型系統[類的聲明]
類型系統:
- 一組基本類型構成的“基本類型集合”;
- “基本類型集合”上定義的一系列組合、運算、轉換方法。
類型系統包括基礎類型(byte、int、bool、float等);複合類型(數組、結構體、指針等);可以指向任何對象的類型(Any類型,類似Java的Object類型);值語義和引用語義;面向對象類型;接口。Go大多數類型爲值語義,可以給任何類型添加方法(包括內置類型,不包括指針類型)。Any類型是空接口即interface{}。
1.方法
1、爲類型添加方法[類方法聲明],方法即爲有接收者的函數
func (對象名 對象類型) 函數名(參數列表) (返回值列表)
可隨時爲某個對象添加方法即爲某個方法添加歸屬對象(receiver),以方法爲中心
在Go語言中沒有隱藏的this指針,即顯示傳遞,形參即爲this,例如以下的形參爲a。
type Integer int func (a Integer) Less(b Integer) bool{ //表示a這個對象定義了Less這個方法,a可以爲任意類型 return a<b } //類型基於值傳遞,如果要修改值需要傳遞指針 func (a *Integer) Add(b Integer){ *a+=b //通過指針傳遞來改變值 } |
2.值語義和引用語義
值類型:b的修改並不會影響a的值
引用類型:b的修改會影響a的值
Go大多類型爲值語義,包括基本類型:byte,int,string等;複合類型:數組,結構體(struct),指針等
//2、值語義和引用語義 b=a b.Modify() //值類型 var a=[ 3 ] int { 1 , 2 , 3 } b:=a b[ 1 ]++ fmt.Println(a,b) //a=[1,2,3] b=[1,3,3] //引用類型 a:=[ 3 ] int { 1 , 2 , 3 } b:=&a //b指向a,即爲a的地址,對b指向的值改變實際上就是對a的改變(數組本身就是一種地址指向) b[ 1 ]++ fmt.Println(a,*b) //a=[1,3,3] b=[1,3,3] //*b,取地址指向的值 |
3.結構體
3、結構體[類屬性的聲明]
struct的功能類似Java的class,可實現嵌套組合(類似繼承的功能)
struct實際上就是一種複合類型,只是對類中的屬性進行定義賦值,並沒有對方法進行定義,方法可以隨時定義綁定到該類的對象上,更具靈活性。可利用嵌套組合來實現類似繼承的功能避免代碼重複。
type Rect struct{ //定義矩形類 x,y float64 //類型只包含屬性,並沒有方法 width,height float64 } func (r *Rect) Area() float64{ //爲Rect類型綁定Area的方法,*Rect爲指針引用可以修改傳入參數的值 return r.width*r.height //方法歸屬於類型,不歸屬於具體的對象,聲明該類型的對象即可調用該類型的方法 } |
(二)初始化[實例化對象]
數據初始化的內建函數new()與make(),二者都是用來分配空間。區別如下:
- new()
- func new(Type) *Type
- 內置函數
new
分配空間。傳遞給new
函數的是一個類型,不是一個值。返回值是指向這個新分配的零值的指針
- make()
- func make(Type, size IntegerType) Type
- 內建函數
make
分配並且初始化 一個 slice, 或者 map 或者 chan 對象。 並且只能是這三種對象。 和new
一樣,第一個參數是 類型,不是一個值。 但是make
的返回值就是這個類型(即使一個引用類型),而不是指針。 具體的返回值,依賴具體傳入的類型。
//創建實例 rect1:= new (Rect) //new一個對象 rect2:=&Rect{} //爲賦值默認值,bool默認值爲false,int默認爲零值0,string默認爲空字符串 rect3:=&Rect{ 0 , 0 , 100 , 200 } //取地址並賦值,按聲明的變量順序依次賦值 rect4:=&Rect{width: 100 ,height: 200 } //按變量名賦值不按順序賦值 //構造函數:沒有構造參數的概念,通常由全局的創建函數NewXXX來實現構造函數的功能 func NewRect(x,y,width,height float64) *Rect{ return &Rect{x,y,width,height} //利用指針來改變傳入參數的值達到類似構造參數的效果 } //方法的重載,Go不支持方法的重載(函數同名,參數不同) //v …interface{}表示參數不定的意思,其中v是slice類型,及聲明不定參數,可以傳入任意參數,實現類似方法的重載 func (poem *Poem) recite(v ... interface {}) { fmt.Println(v) } |
(三)匿名組合[繼承]
組合,即方法代理,例如A包含B,即A通過消息傳遞的形式代理了B的方法,而不需要重複寫B的方法。
繼承是指這樣一種能力:它可以使用現有類的所有功能,並在無需重新編寫原來的類的情況下對這些功能進行擴展。繼承主要爲了代碼複用,繼承也可以擴展已存在的代碼模塊(類)。
嚴格來講,繼承是“a kind of ”,即子類是父類的一種,例如student是person的一種;組合是“a part of”,即父類是子類中的一部分,例如眼睛是頭部的一部分。
//1、匿名組合的方式實現了類似Java繼承的功能,可以實現多繼承 type Base struct{ Name string } func (base *Base) Foo(){...} //Base的Foo()方法 func (base *Base) Bar(){...} //Base的Bar()方法 type Foo struct{ Base //通過組合的方式聲明瞭基類,即繼承了基類 ... } func (foo *Foo) Bar(){ foo.Base.Bar() //並改寫了基類的方法,該方法實現時先調用基類的Bar()方法 ... //如果沒有改寫即爲繼承,調用foo.Foo()和調用foo.Base.Foo()的作用的一樣的 } //修改內存佈局 type Foo struct{ ... //其他成員信息 Base } //以指針方式組合 type Foo struct{ *Base //以指針方式派生,創建Foo實例時,需要外部提供一個Base類實例的指針 ... } //名字衝突問題,組合內外如果出現名字重複問題,只會訪問到最外層,內層會被隱藏,不會報錯,即類似java中方法覆蓋/重寫。 type X struct{ Name string } type Y struct{ X //Y.X.Name會被隱藏,內層會被隱藏 Name string //只會訪問到Y.Name,只會調用外層屬性 } |
(四)可見性[封裝]
封裝,也就是把客觀事物封裝成抽象的類,並且類可以把自己的數據和方法只讓可信的類或者對象操作,對不可信的進行信息隱藏。
封裝的本質或目的其實程序對信息(數據)的控制力。封裝分爲兩部分:該隱藏的隱藏,該暴露的暴露。封裝可以隱藏實現細節,使得代碼模塊化。
Go中用大寫字母開頭來表示public,可以包外訪問;小寫字母開頭來表示private,只能包內訪問;訪問性是包級別非類型級別
如果可訪問性是類型一致的,可以加friend關鍵字表示朋友關係可互相訪問彼此的私有成員(屬性和方法)
type Rect struct{ X,Y float64 Width,Height float64 //字母大寫開頭表示該屬性可以由包外訪問到 } func (r *Rect) area() float64{ //字母小寫開頭表示該方法只能包內調用 return r.Width*r.Height } |
(五)接口[多態]
多態性(polymorphisn)是允許你將父對象設置成爲和一個或更多的他的子對象相等的技術,賦值之後,父對象就可以根據當前賦值給它的子對象的特性以不同的方式運作。
簡而言之,就是允許將子類類型的指針賦值給父類類型的指針。
即一個引用變量倒底會指向哪個類的實例對象,該引用變量發出的方法調用到底是哪個類中實現的方法,必須在由程序運行期間才能決定。不修改程序代碼就可以改變程序運行時所綁定的具體代碼,讓程序可以選擇多個運行狀態,這就是多態性。多態分爲編譯時多態(靜態多態)和運行時多態(動態多態),編譯時多態一般通過方法重載實現,運行時多態一般通過方法重寫實現。
5.1接口概念
接口即一組方法的集合,定義了對象的一組行爲,方法包含實際的代碼。換句話說,一個接口就是定義(規範或約束),而方法就是實現,接口的作用應該是將定義與實現分離,降低耦合度。習慣用“er”結尾來命名,例如“Reader”。接口與對象的關係是多對多,即一個對象可以實現多個接口,一個接口也可以被多個對象實現。
接口是Go語言整個類型系統的基石,其他語言的接口是不同組件之間的契約的存在,對契約的實現是強制性的,必須顯式聲明實現了該接口,這類接口稱之爲“侵入式接口”。而Go語言的接口是隱式存在,只要實現了該接口的所有函數則代表已經實現了該接口,並不需要顯式的接口聲明。
- 接口的比喻
你的電腦上只有一個USB接口。這個USB接口可以接MP3,數碼相機,攝像頭,鼠標,鍵盤等。。。所有的上述硬件都可以公用這個接口,有很好的擴展性,該USB接口定義了一種規範,只要實現了該規範,就可以將不同的設備接入電腦,而設備的改變並不會對電腦本身有什麼影響(低耦合)。
- 面向接口編程
接口表示調用者和設計者的一種約定,在多人合作開發同一個項目時,事先定義好相互調用的接口可以大大提高開發的效率。接口是用類來實現的,實現接口的類必須嚴格按照接口的聲明來實現接口提供的所有功能。有了接口,就可以在不影響現有接口聲明的情況下,修改接口的內部實現,從而使兼容性問題最小化。
當其他設計者調用了接口後,就不能再隨意更改接口的定義,否則項目開發者事先的約定就失去了意義。但是可以在類中修改相應的代碼,完成需要改動的內容。
5.2非侵入式接口
非侵入式接口:一個類只需要實現了接口要求的所有函數就表示實現了該接口,並不需要顯式聲明
type File struct{ //類的屬性 } //File類的方法 func (f *File) Read(buf [] byte ) (n int ,err error) func (f *File) Write(buf [] byte ) (n int ,err error) func (f *File) Seek(off int64,whence int ) (pos int64,err error) func (f *File) Close() error //接口1:IFile type IFile interface { Read(buf [] byte ) (n int ,err error) Write(buf [] byte ) (n int ,err error) Seek(off int64,whence int ) (pos int64,err error) Close() error } //接口2:IReader type IReader interface { Read(buf [] byte ) (n int ,err error) } //接口賦值,File類實現了IFile和IReader接口,即接口所包含的所有方法 var file1 IFile = new (File) var file2 IReader = new (File) |
5.3接口賦值
只要類實現了該接口的所有方法,即可將該類賦值給這個接口,接口主要用於多態化方法。即對接口定義的方法,不同的實現方式。
接口賦值:
1)將對象實例賦值給接口
type IUSB interface{ //定義IUSB的接口方法 } //方法定義在類外,綁定該類,以下爲方便,備註寫在類中 type MP3 struct { //實現IUSB的接口,具體實現方式是MP3的方法 } type Mouse struct { //實現IUSB的接口,具體實現方式是Mouse的方法 } //接口賦值給具體的對象實例MP3 var usb IUSB = new (MP3) usb.Connect() usb.Close() //接口賦值給具體的對象實例Mouse var usb IUSB = new (Mouse) usb.Connect() usb.Close() |
2)將接口賦值給另一個接口
- 只要兩個接口擁有相同的方法列表(與次序無關),即是兩個相同的接口,可以相互賦值
- 接口賦值只需要接口A的方法列表是接口B的子集(即假設接口A中定義的所有方法,都在接口B中有定義),那麼B接口的實例可以賦值給A的對象。反之不成立,即子接口B包含了父接口A,因此可以將子接口的實例賦值給父接口。
- 即子接口實例實現了子接口的所有方法,而父接口的方法列表是子接口的子集,則子接口實例自然實現了父接口的所有方法,因此可以將子接口實例賦值給父接口。
type Writer interface{ //父接口 Write(buf []byte) (n int ,err error) } type ReadWriter interface{ //子接口 Read(buf []byte) (n int ,err error) Write(buf []byte) (n int ,err error) } var file1 ReadWriter= new (File) //子接口實例 var file2 Writer=file1 //子接口實例賦值給父接口 |
5.4接口查詢
若要在 switch 外判斷一個接口類型是否實現了某個接口,可以使用“逗號 ok ”。
value, ok := Interfacevariable.(implementType)
其中 Interfacevariable 是接口變量(接口值),implementType 爲實現此接口的類型,value 返回接口變量實際類型變量的值,如果該類型實現了此接口返回 true。
//判斷file1接口指向的對象實例是否是File類型 var file1 Writer=... if file5,ok:=file1.(File);ok{ ... } |
5.5接口類型查詢
在 Go 中,要判斷傳遞給接口值的變量類型,可以在使用 type switch 得到。(type)只能在 switch 中使用。
// 另一個實現了 I 接口的 R 類型 type R struct { i int } func (p *R) Get() int { return p.i } func (p *R) Put(v int ) { p.i = v } func f(p I) { switch t := p.(type) { // 判斷傳遞給 p 的實際類型 case *S: // 指向 S 的指針類型 case *R: // 指向 R 的指針類型 case S: // S 類型 case R: // R 類型 default : //實現了 I 接口的其他類型 } } |
5.6接口組合
//接口組合類似類型組合,只不過只包含方法,不包含成員變量 type ReadWriter interface { //接口組合,避免代碼重複 Reader //接口Reader Writer //接口Writer } |
5.7Any類型[空接口]
每種類型都能匹配到空接口:interface{}。空接口類型對方法沒有任何約束(因爲沒有方法),它能包含任意類型,也可以實現到其他接口類型的轉換。如果傳遞給該接口的類型變量實現了轉換後的接口則可以正常運行,否則出現運行時錯誤。
//interface{}即爲可以指向任何對象的Any類型,類似Java中的Object類 var v1 interface {}=struct{X int }{ 1 } var v2 interface {}= "abc" func DoSomething(v interface {}) { //該函數可以接收任何類型的參數,因爲任何類型都實現了空接口 // ... } |
5.8接口的代碼示例
//接口animal type Animal interface { Speak() string } //Dog類實現animal接口 type Dog struct { } func (d Dog) Speak() string { return "Woof!" } //Cat類實現animal接口 type Cat struct { } func (c Cat) Speak() string { return "Meow!" } //Llama實現animal接口 type Llama struct { } func (l Llama) Speak() string { return "?????" } //JavaProgrammer實現animal接口 type JavaProgrammer struct { } func (j JavaProgrammer) Speak() string { return "Design patterns!" } //主函數 func main() { animals := []Animal{Dog{}, Cat{}, Llama{}, JavaProgrammer{}} //利用接口實現多態 for _, animal := range animals { fmt.Println(animal.Speak()) //打印不同實現該接口的類的方法返回值 } } |