1. 程式人生 > >Go基礎系列:struct的匯出和暴露問題

Go基礎系列:struct的匯出和暴露問題

struct的匯出和暴露問題

關於struct的匯出

struct的屬性是否被匯出,也遵循大小寫的原則:首字母大寫的被匯出,首字母小寫的不被匯出。

所以,如果struct名稱首字母是小寫的,這個struct不會被匯出。連同它裡面的欄位也不會匯出,即使有首字母大寫的欄位名。

如果struct名稱首字母大寫,則struct會被匯出,但只會匯出它內部首字母大寫的欄位,那些小寫首字母的欄位不會被匯出。

也就是說,struct的匯出情況是混合的。

但並非絕對如此,如果struct嵌套了,那麼即使被巢狀在內部的struct名稱首字母小寫,也能訪問到它裡面首字母大寫的欄位。

例如:

type animal struct{
    name string
    Speak string
}
type Horse struct {
    animal
    sound string
}

Horse中巢狀的animal是小寫字母開頭的,但Horse是能被匯出的,所以能在其它包中使用Horse struct,其他包也能訪問到animal中的Speak屬性。

很多時候,Horse這個名字是不安全的,因為這表示匯出Horse這個struct給其他包,也就是將Horse給暴露出去了,外界可以直接開啟Horse這個"黑匣子"。

但如果不將Horse匯出,如何能在其它包構建出Horse例項?見下文。

不要暴露struct

很多時候,不應該將某包(如包abc)中的struct(如animal)直接暴露給其它包,暴露意味著打開了那個"黑匣子",所以struct會以小寫字母開頭,不將其匯出。

這時在外界其它包中構建包abc的animal,就沒法直接通過以下幾種方式實現:

  • var xxx abc.animal
  • new(abc.animal)
  • &abc.animal{...}
  • abc.animal{...}

例如,下面的是錯誤的:

// abc/abc.go檔案內容:
package abc

type animal struct{
    name string
    Speak string
}

// test.go內容:
package main

import "./abc"

func main() {
    // 全都錯誤
    var t1 abc.animal
    t2 := new(abc.animal)
    t3 := &abc.animal{}
    t4 := abc.animal{}
}

那麼如何在外界構建隱藏起來的struct例項?這時可以在abc包中寫一個可匯出的函式,通過這個函式來構建struct例項。例如:

// abc/abc.go檔案內容:
package abc

type animal struct{
    name string
    Speak string
}

func NewAnimal() *animal{
    a := new(animal)
    return a
}


// test.go內容:
package main

import (
    "fmt"
    "./abc"
)

func main() {
    t1 := abc.NewAnimal()
//  t1.name = "haha"    // 無法訪問name屬性
    t1.Speak = "hhhh"
    fmt.Println(t1.Speak)
}

上面的程式碼一切正常,在main包中可以通過NewAnimal()構建出abc包中未匯出的animal struct。注意,上面NewAnimal()中是使用new()函式構造例項的,它返回的是例項的指標,至於如何構造例項,完全可以根據自己的需求,但對於struct型別來說,一般都是使用指標的,也就是完全可以將new()通用化。

由於animal中的name欄位是不匯出的欄位,所以在外界即便是通過NewAnimal()構建出了animal例項,也無法訪問該例項的name屬性,所以沒法為name欄位賦值。換句話說,name屬性永遠是初始化的0值。

因此,為了讓構建例項時自定義name屬性,需要在構造方法NewAnimal()上指定設定給name屬性的引數。修改NewAnimal()函式:

func NewAnimal(name string) *animal{
    a := new(animal)
    a.name = name
    return a
}

然後在其它包中構建animal例項:

t1 := abc.NewAnimal("longshuai")

雖然其它包中構建的animal例項已經具備了name屬性,但還是無法訪問該例項的name屬性。所以,在abc包中繼續寫一個可匯出的方法,該方法用於獲取例項的name屬性:

// abc/abc.go中新增:
func (a *animal) GetName() string {
    return a.name
}

於是外界包中可以通過這個匯出的方法獲取例項的name屬性:

t1 := abc.NewAnimal("longshuai")
fmt.Println(abc.GetName())

實際上,上面NewAnimal()構造物件時,可以不用傳遞name引數,而是像GetName()一樣,寫一個專門的可匯出方法來設定例項的name屬性。改寫abc/abc.go中的程式碼:

func NewAnimal() *animal{
    a := new(animal)
    return a
}
func (a *animal) SetName(name string){
    a.name = name
}

現在,abc/abc.go中的animal struct就完全對外隱藏了。