天天看點

Golang系列(二)之面向對象程式設計

本文個人部落格位址:http://www.huweihuang.com/article/golang/golang-object-oriented-programming/

  • 面向對象程式設計:

       把一組資料結構和處理它們的方法組成對象(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{

,

,

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())  

//列印不同實作該接口的類的方法傳回值

}

}