天天看點

Go語言面組合式向對象程式設計基礎總結

Go語言的面向對象程式設計簡單而幹淨,通過非侵入式接口模型,否定了C/C++ Java C#等傳統面向對象程式設計語言的複雜度的必要性,我們發現在Go中即使簡單的組合也能達到傳統面向對象語言的效果,而且耦合度非常低,按照Go的作者之一也就是C語言的作者之一說的一句話:Go是更好的C語言。

1、Go中任意類型Any即  interface{}類型,也就是空接口,可以指派為任意類型

2、可以為其他類型 内置類型 不包括指針類型添加相應的方法 但是注意的一點是一定要用别名。。進行包裝

記住想要 為類型 添加新的方法 那麼請把類型定義别名,别名和原來的類型就不一樣了成了新類型  

在Go中隻能對非本地類型添加方法......也就是int不能添加方法 需要 type Int int 才可以為Int添加方法

package main

import(

   "fmt"

)

type Integer int

func (a Integer) Less(b Integer)bool {

    return a<b

}

func main(){

   var a Integer=1

   r:=a.Less(2)

   fmt.Println(r)

/////用法2 組合之指針

func (a *Integer) Less(b Integer)bool {

    *a-=10

    return *a<b

type Int int

func (a *Int) LessIne(b Int)bool{

   var aa int=10

   a=Integer(aa)

3、go中不存在this指針,通過文法糖顯式傳遞對象

在Go語言中沒有隐藏的this指針”這句話的含義是:

 方法施加的目标(也就是“對象” )顯式傳遞,沒有被隐藏起來;

 方法施加的目标(也就是“對象” )不需要非得是指針,也不用非得叫this。 

4、用type定以後的别名類型就是新類型了 隻有強制轉換才能使用

   a=Integer(aa)  //不強制轉換會編譯出錯的

5、Go中的值語義和引用   

channel map本質是指針 因為複制他們沒有意義  ........數組切片 []type 本質上是值類型 interface接口非常重要

值語義和引用語義的差别在于指派,比如下面的例子:

b = a

b.Modify()

如果b的修改不會影響a的值,那麼此類型屬于值類型。如果會影響a的值,那麼此類型是引用

類型。

Go語言中的大多數類型都基于值語義,包括:

 基本類型,如byte、int、bool、float32、float64和string等;

 複合類型,如數組(array) 、結構體(struct)和指針(pointer)等。

Go語言中類型的值語義表現得非常徹底。我們之是以這麼說,是因為數組。

如果讀者之前學過C語言,就會知道C語言中的數組比較特别。通過函數傳遞一個數組的時

候基于引用語義, 但是在結構體中定義數組變量的時候基于值語義 (表現在為結構體指派的時候,

該數組會被完整地複制) 。

Go語言中的數組和基本類型沒有差別,是很純粹的值類型,例如:

var a = [3]int{1, 2, 3}

var b = a

b[1]++

fmt.Println(a, b)

該程式的運作結果如下:

[1 2 3] [1 3 3]。

這表明b=a指派語句是數組内容的完整複制。要想表達引用,需要用指針:

var b = &a

fmt.Println(a, *b)

[1 3 3] [1 3 3]

這表明b=&a指派語句是數組内容的引用。變量b的類型不是[3]int,而是*[3]int類型。

Go語言中有4個類型比較特别,看起來像引用類型,如下所示

數組切片:指向數組(array)的一個區間。

 map:極其常見的資料結構,提供鍵值查詢能力。

 channel:執行體(goroutine)間的通信設施。

 接口(interface) :對一組滿足某個契約的類型的抽象。

但是這并不影響我們将Go語言類型看做值語義。下面我們來看看這4個類型。

數組切片本質上是一個區間,你可以大緻将[]T表示為:

type slice struct {

    first *T

    len int

    cap int

因為數組切片内部是指向數組的指針,是以可以改變所指向的數組元素并不奇怪。數組切片

類型本身的指派仍然是值語義。

map本質上是一個字典指針,你可以大緻将map[K]V表示為:

type Map_K_V struct {

// ...

type map[K]V struct {

    impl *Map_K_V

基于指針,我們完全可以自定義一個引用類型,如:

type IntegerRef struct {

   impl *int

channel和map類似,本質上是一個指針。将它們設計為引用類型而不是統一的值類型的原因

是,完整複制一個channel或map并不是正常需求。

同樣,接口具備引用語義,是因為内部維持了兩個指針,示意為:

type interface struct {

    data *void

    itab *Itab

接口在Go語言中的地位非常重要。關于接口的内部實作細節,在後面的高階話題中我們再

細細剖析。 

7、Go語言中的結構體  組合非繼承

Go語言的結構體(struct)和其他語言的類(class)有同等的地位,但Go語言放棄了包括繼

承在内的大量面向對象特性,隻保留了組合(composition)這個最基礎的特性。

組合甚至不能算面向對象特性,因為在C語言這樣的過程式程式設計語言中,也有結構體,也有

組合。組合隻是形成複合類型的基礎。

上面我們說到,所有的Go語言類型(指針類型除外)都可以有自己的方法。在這個背景下,

Go語言的結構體隻是很普通的複合類型,平淡無奇。例如,我們要定義一個矩形類型:

type Rect struct {

    x, y float64

    width, height float64

然後我們定義成員方法Area()來計算矩形的面積:

func (r *Rect) Area() float64 {

    return r.width * r.height

可以看出, Go語言中結構體的使用方式與C語言并沒有明顯不同。 

8、Go中的組合精華  建立結構體指針 和為 結構體擴充成員函數的時候.....傳遞 值類型和指針類型的差別

import "fmt"

type Rect struct{

    x,y float64

    width,height float64

///如果寫成rct Rect 那麼内部的修改不會影響到 外部結構

///如果寫成rct*Rect那麼内部的修改會影響到外部結構的值 這就是 指針的效果

func (rct *Rect)Area() float64{

     rct.width=1000 ///也可以(*rct).width=1000一樣

    return rct.width*rct.height

     rct:=new(Rect)

//對于結構體指針,...調用方法和值類型一樣直接.唯一的差別是 作為參數傳遞的時候 傳遞的是位址 值可以被修改 

//是以進行組合的時候就有兩種選擇 

   // 可以寫成 var rct*Rect=&Rect{}  

  //也可以寫成 var rct Rect=Rect{}  

 //var rct *Rect=new(Rect)  

//也可以寫成 var rct Rect=Rect{1,2,3,4}  

     rct.width=10.0

     rct.height=10.0

     area:=rct.Area()

     fmt.Println(area)

     fmt.Println(rct.width)

9、普通的組合繼承 ...........................以及組合指針繼承 以及覆寫 和函數 成員名字沖突 

//通過值類型繼承

//Go中繼承屬于匿名組合 .......可以從對象繼承 也可以從指針匿名繼承...

//匿名繼承會去掉包名,,,是以不能同時繼承類名相同的  即使不在同一個包中

type Base struct{  

    Name string

    Age  uint8

///為Base結構體組合進去兩個函數

func (pBase*Base) showName(){

     fmt.Println("Age",pBase.Name)

func (pBase*Base) showAge(){

     fmt.Println("Age",pBase.Age)

//建立Sub結構體

type Sub struct{

    //組合Base修改記憶體模型

    //匿名組合進Base 對于調用者是不知道的

    //即使我們覆寫了 Base的方法 但是我們還是可以通過xxx.Base.xxx()調用基類的方法的

   //如果是*Base我們需要在調用處手動添加new Base 否則運作會出錯的

    Base  

func (pSub*Sub) showName(){

  fmt.Println("Before Sub ShowName")

    pSub.Base.showName()

    fmt.Println("After Sub ShowName")

      obj:=new(Sub)

      obj.Name="張三"

      obj.Age=15

      obj.showName()

      obj.showAge()

///通過指針類型繼承

    *Base  

    //由于使用指針繼承是以 我們要設定匿名組合模闆的記憶體對象 位址

      obj.Base=&Base{}

10、Go語言的可見性 權限是包一級的,包外的不能通路包内的小寫開頭成員......包内無所謂

11、Go的非侵入式接口 和實作     

/////Go語言會為每一個成員函數 自動生成對應的函數  比如 func(a *A) 會自動生成 func (a A) .....

///反過來則不行 因為 func (a A)這時候傳遞的是形參  (&a).xx()改變的是 參數 副本 而不是 外部類

///非侵入式接口

////接口 和實作完全分析 減少耦合

///實作方隻負責實作  接口方隻負責封裝自己的借口就行...實作方甚至不知道 有這個接口的存在 這就是 Go的 非侵入式接口的特點

type IFly interface{

    fly()

type ISay interface{

    say()

type Bird struct{

//由于匿名傳遞進來的是指針類型 所對于接口的指派必須是 指針

func (pBird*Bird) fly(){

   fmt.Println("i am a bird, i can fly()!")

//由于匿名傳遞的不是指針類型是值類型 是以接口指派 可以不是指針而是值

func (pBird Bird) say(){

   fmt.Println("i am a bird, i can say()!")

     birdObj:=Bird{}

     var iFly IFly=&birdObj

     iFly.fly()

     var iSay ISay=birdObj

     iSay.say()

13、接口之間是可以互相指派的

實作了相同方法的接口可以互相指派,如果接口B是A非超集,那麼  A可以指派為B

對象不可以被指派為接口 ,繁殖接口可以被指派為實作了 某些方法的對象 或者包含他方法的 接口對象

type IFly1 interface{

type ISay1 interface{

     ////接口之間的指派

     var iFly1 IFly1=iFly

     iFly1.fly()

14、Go中的值類型非常的徹底  數組都是值類型

15、關于給類型添加String()方法   相當于 其他語言的toString 用于列印輸出

func (pBird Bird) String() string{

   return "aaaaaaaaaa"

     fmt.Println(birdObj)

16、接口的組合 就是把多個接口組合到一起......接口中隻有函數沒有屬性

type ISay_Fly interface{

    ISay

    IFly

17、接口查詢 obj,ok=val.(Interface)   傳回查詢的接口 并且傳回查詢結果 

x.(type) 擷取類型 隻能在switch中用 

x.(OterTypeInterface) 判斷x是否是指定接口類型 傳回指定接口對象,和查詢結果

在Go語言中,還可以更加直截了當地詢問接口指向的對象執行個體的類型,例如:

var v1 interface{} = ...

switch v := v1.(type) {

    case int:    // 現在v的類型是int

    case string: // 現在v的類型是string 

...

就像現實生活中物種多得數不清一樣,語言中的類型也多得數不清,是以類型查詢并不經常

使用。它更多是個補充,需要配合接口查詢使用,例如:

type Stringer interface {

    String() string

func Println(args ...interface{}) {

    for _, arg := range args {  

        switch v := v1.(type) {

            case int:                        // 現在v的類型是int

            case string:                     // 現在v的類型是string

            default:

            if v, ok := arg.(Stringer); ok { // 現在v的類型是Stringer

                val := v.String()

                // ...

            } else {

            }

        }

    }

當然,Go語言标準庫的Println()比這個例子要複雜很多,我們這裡隻摘取其中的關鍵部

分進行分析。對于内置類型,Println()采用窮舉法,将每個類型轉換為字元串進行列印。對

于更一般的情況,首先确定該類型是否實作了String()方法,如果實作了,則用String()方

法将其轉換為字元串進行列印。否則,Println()利用反射功能來周遊對象的所有成員變量進

行列印。

是的,利用反射也可以進行類型查詢,詳情可參閱reflect.TypeOf()方法的相關文檔。此

外,

18、 Any類型  對于匿名結構體指派給任意類型  沒法取出 具體每個匿名結構體的内部屬性 隻能前部列印 通過系統預設的String()函數

      var any1 interface{}=1

     var any2 interface{}="b"

     var any3 interface{}=struct{x ,y string}{"hello,world","aaaaa"}

     fmt.Println(any1,any2,any3)

由于Go語言中任何對象執行個體都滿足空接口interface{},是以interface{}看起來像是可

以指向任何對象的Any類型,如下:

var v1 interface{} = 1       // 将int類型指派給interface{}

var v2 interface{} = "abc"   // 将string類型指派給interface{}

var v3 interface{} = &v2     // 将*interface{}類型指派給interface{}

var v4 interface{} = struct{ X int }{1}

var v5 interface{} = &struct{ X int }{1}

當函數可以接受任意的對象執行個體時,我們會将其聲明為interface{},最典型的例子是标

準庫fmt中PrintXXX系列的函數,例如:

func Printf(fmt string, args ...interface{})

func Println(args ...interface{})

總體來說,interface{}類似于COM中的IUnknown,我們剛開始對其一無所知,但可以通

過接口查詢和類型查詢逐漸了解它。