Go面向對象編程

  • 面向對象編程:

       把一組數據結構和處理它們的方法組成對象(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()
  1. func new(Type) *Type
  2. 內置函數 new 分配空間。傳遞給new 函數的是一個類型,不是一個值。返回值是指向這個新分配的零值的指針
  • make()
  1. func make(Type, size IntegerType) Type 
  2. 內建函數 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)將接口賦值給另一個接口

  1. 只要兩個接口擁有相同的方法列表(與次序無關),即是兩個相同的接口,可以相互賦值
  2. 接口賦值只需要接口A的方法列表是接口B的子集(即假設接口A中定義的所有方法,都在接口B中有定義),那麼B接口的實例可以賦值給A的對象。反之不成立,即子接口B包含了父接口A,因此可以將子接口的實例賦值給父接口。
  3. 即子接口實例實現了子接口的所有方法,而父接口的方法列表是子接口的子集,則子接口實例自然實現了父接口的所有方法,因此可以將子接口實例賦值給父接口。
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())  //打印不同實現該接口的類的方法返回值
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章