Go In Action --- 類型的本質(zhì)、接口、多態(tài)、嵌入類型、標(biāo)識(shí)符

類型

在聲明一個(gè)新類型之后,聲明一個(gè)該類型的方法之前,需先確定:這個(gè)類型的本質(zhì)是什么?如果給這個(gè)類型增加或刪除某個(gè)值,是要?jiǎng)?chuàng)建一個(gè)新值,還是要更改當(dāng)前的值?如果要新值,就選擇值接收者;如果要修改當(dāng)前值,就選擇指針接收者。這決定了 程序內(nèi)部傳遞這個(gè)類型的值的方式:是按值做傳遞,還是按指針做傳遞。

1. 內(nèi)置類型

包含數(shù)值類型、字符串類型和布爾類型。
這些類型本質(zhì)上都是原始類型,在操作其值時(shí),應(yīng)該傳遞其對(duì)應(yīng)的值的副本。

2. 引用類型

包含切片、映射、通道、接口和函數(shù)類型。
每個(gè)引用類型創(chuàng)建的標(biāo)頭值(引用類型創(chuàng)建的變量),都包含一個(gè)指向底層數(shù)據(jù)結(jié)構(gòu)的指針。
所以通過復(fù)制來傳遞一個(gè)引用類型的值的副本,本質(zhì)上就是在共享底層數(shù)據(jù)結(jié)構(gòu)。

3. 結(jié)構(gòu)類型

待補(bǔ)充
結(jié)構(gòu)類型的本質(zhì)既可以是原始的,也可以是非原始的。

4. 看一個(gè)標(biāo)準(zhǔn)庫中的接口 及 實(shí)現(xiàn)例子

4.1 io.Writer接口

// Writer is the interface that wraps the basic Write method.
//
// Write writes len(p) bytes from p to the underlying data stream.
// It returns the number of bytes written from p (0 <= n <= len(p))
// and any error encountered that caused the write to stop early.
// Write must return a non-nil error if it returns n < len(p).
// Write must not modify the slice data, even temporarily.
//
// Implementations must not retain p.
type Writer interface {
    Write(p []byte) (n int, err error)
}

4.2 bytes.Buffer實(shí)現(xiàn)了該接口

// Write appends the contents of p to the buffer, growing the buffer as
// needed. The return value n is the length of p; err is always nil. If the
// buffer becomes too large, Write will panic with ErrTooLarge.
func (b *Buffer) Write(p []byte) (n int, err error) {
    b.lastRead = opInvalid
    m := b.grow(len(p))
    return copy(b.buf[m:], p), nil
}

4.3 接口實(shí)現(xiàn)多態(tài)

當(dāng)方法的接收參數(shù)是某個(gè)接口時(shí),可以將不同的實(shí)現(xiàn)該接口的值作為參數(shù)傳遞給方法,從而實(shí)現(xiàn)采取不同行為的能力。
方法fmt.Fprintf的第一個(gè)參數(shù)是io.Writer接口

// Fprintf formats according to a format specifier and writes to w.
// It returns the number of bytes written and any write error encountered.
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
    p := newPrinter()
    p.doPrintf(format, a)
    n, err = w.Write(p.buf)
    p.free()
    return
}

方法io.Copy的第一個(gè)參數(shù)也是io.Writer接口

func Copy(dst Writer, src Reader) (written int64, err error) {
    return copyBuffer(dst, src, nil)
}

4.4 簡單的例子展示io多態(tài)

func ByteBuffer()  {
    var b bytes.Buffer

    //將字符串寫入Buffer
    b.Write([]byte("Hello go"))

    //使用Fprintf將字符串拼接到Buffer
    fmt.Fprintf(&b, ", nice to meet you")

    //將Buffer的內(nèi)容寫到Stdout
    io.Copy(os.Stdout, &b)
}

最終輸出結(jié)果:
Hello go, nice to meet you

5. Go語言的接口規(guī)則

因?yàn)樯婕暗街羔?,?guī)則相比Java來說略微復(fù)雜一些。

Go語言的方法有接收者這一說詞,從Java的角度來看,就是方法的調(diào)用者。在Java中,調(diào)用者就是調(diào)用者。但是Go語言中,分值接收者指針接收者。但規(guī)則不外乎如下兩種:

  1. 如果一個(gè)接口的實(shí)現(xiàn)類型,用指針接收者來作為方法的接收者(其實(shí)就是Java的調(diào)用者),那么只能用該類型的指針對(duì)象來調(diào)用該接口。

  2. 如果一個(gè)接口的實(shí)現(xiàn)類型,用值接收者來作為方法的接收者,那么既可以用指針對(duì)象也可以用值的副本來調(diào)用該接口。

以下是Go語言給出的方法集規(guī)則:

Values Methods Receivers remark
T (t T) 如2
*T (t T) and (t *T) 如1
Methods Receivers Values remark
(t T) T and *T 如2
(t *T) *T 如1

5. 多態(tài)

如下實(shí)現(xiàn)了一個(gè)簡單的多態(tài):

//person接口
type personInter interface {
    sayName()
}

//用戶
type user struct {
    name string
}
func (u *user) sayName()  {
    fmt.Printf("User name is %s \n", u.name)
}

//管理員
type admin struct {
    name string
}
func (u *admin) sayName()  {
    fmt.Printf("Admin name is %s \n", u.name)
}

func Test()  {
    logger := user{"logger"}
    //letsSayYourName(logger)  //這是錯(cuò)誤調(diào)用,不能用值的副本logger去調(diào)用接收者為指針接收者的實(shí)現(xiàn)方法
    letsSayYourName(&logger)

    admin := admin{"admin"}
    letsSayYourName(&admin)
}

//方法入?yún)榻涌冢灰獙?shí)現(xiàn)了該接口的實(shí)現(xiàn)類型都可作為入?yún)?func letsSayYourName(person personInter)  {
    person.sayName()
}

最終輸出:
User name is logger
Admin name is admin

6. 嵌入類型

先介紹一下Go語言所謂的嵌入類型:
嵌入類型是將已有的類型直接聲明在新的結(jié)構(gòu)類型中,被嵌入的類型被稱為新的外部類型的內(nèi)部類型。
個(gè)人理解Go語言中的嵌入類型類似于Java中的組合。

6.1 示例1 -- 外部類型調(diào)用內(nèi)嵌類型的方法

type animal struct {
    name string
}

func (u *animal) sayName() {
    fmt.Printf("name is %s \n", u.name)
}

//管理員類型,內(nèi)嵌一個(gè)user類型
type person struct {
    animal  //嵌入類型
    name string
}

func TestInnerType() {
    ad := person{animal{"logger"}, "admin"}
    ad.animal.sayName()
    ad.sayName()
}

輸出:
name is logger
name is logger

小結(jié):

  • 可以將內(nèi)嵌類型的方法提升到外部類型
  • ad.sayName()調(diào)用內(nèi)嵌類型的方法輸出的還是內(nèi)嵌類型的屬性

6.2 示例2 -- 外部類型調(diào)用內(nèi)嵌類型的實(shí)現(xiàn)接口

//接口
type foodInter interface {
    sayFoodName()
}

//蘋果
type apple struct {
    name string
}

//超市
type market struct {
    apple  //內(nèi)嵌類型 -- 蘋果
}

func (a *apple) sayFoodName()  {
    fmt.Printf("food name is %s \n", a.name)
}

func TestInnerTypeInterface()  {
    walmart := market{ apple{"ApplyX"}}

    //重點(diǎn)是這句
    //用于實(shí)現(xiàn)接口的內(nèi)部類型的方法,被提升到了外部類型
    commonSayName(&walmart)
}

func commonSayName(food foodInter)  {
    food.sayFoodName()
}

輸出:
food name is ApplyX

小結(jié):

  • 由于內(nèi)部類型的提升,內(nèi)部類型實(shí)現(xiàn)的接口會(huì)自動(dòng)提升到外部類型。這意味著由于內(nèi)部類型的實(shí)現(xiàn),外部類型也同樣實(shí)現(xiàn)了這個(gè)接口。

6.3 示例3 -- 外部類型覆蓋內(nèi)嵌類型的實(shí)現(xiàn)接口

相比于示例2,增加了類型market自己的實(shí)現(xiàn)方法以此覆蓋內(nèi)嵌類型的實(shí)現(xiàn)接口,并在類型market中添加了屬性marketName以便輸出展示。

//接口
type foodInter interface {
    sayFoodName()
}

//蘋果
type apple struct {
    name string
}

//超市
type market struct {
    marketName string
    apple  //內(nèi)嵌類型 -- 蘋果
}

func (a *apple) sayFoodName()  {
    fmt.Printf("food name is %s \n", a.name)
}

func (m *market) sayFoodName()  {
    fmt.Printf("market name is %s \n", m.marketName)
}

func TestInnerTypeInterface()  {
    walmart := market{ "walmart", apple{"ApplyX"}}

    //重點(diǎn)是這句
    //用于實(shí)現(xiàn)接口的內(nèi)部類型的方法,被提升到了外部類型
    commonSayName(&walmart)
}

func commonSayName(food foodInter)  {
    food.sayFoodName()
}

輸出:
market name is walmart

小結(jié):

  • 由于內(nèi)部類型的提升,內(nèi)部類型實(shí)現(xiàn)的接口會(huì)自動(dòng)提升到外部類型。這意味著由于內(nèi)部類型的實(shí)現(xiàn),外部類型也同樣實(shí)現(xiàn)了這個(gè)接口。

7. 公開或未公開的標(biāo)識(shí)符

類似于Java中的public、private等,你可以控制方法或變量的訪問權(quán)限。Go語言當(dāng)然也提供了這種功能。
Go語言支持從包里公開或隱藏標(biāo)識(shí)符。如果一個(gè)標(biāo)識(shí)符,如下示例中的counter以小寫字母開頭,即包外代碼不可見(類似Java的private),如果一個(gè)標(biāo)識(shí)符,如下示例中的PublicCounter以大寫字母開頭,這個(gè)標(biāo)識(shí)符就是公開的,即包外代碼可見(類似Java的public)。

7.1 原始類型

//聲明一個(gè)未公開的計(jì)數(shù)器
type counter int

//聲明一個(gè)公開的計(jì)數(shù)器
type PublicCounter int

當(dāng)然,我們也可以給未公開的標(biāo)識(shí)符,如counter寫一個(gè)公開的取值函數(shù),或者寫一個(gè)工廠函數(shù)新建一個(gè)counter。

//取值函數(shù)
func GetCounter(value int) counter {
  return counter
}

//工廠函數(shù)
func New(value int) counter {
  return counter(value)
}

工廠函數(shù)命名為New只是Go語言中的一個(gè)習(xí)慣。

7.2 結(jié)構(gòu)類型中的未公開字段

結(jié)構(gòu)類型中帶有未公開類型的字段

//公開的用戶類型
type Admin struct {
    Name string    //公開的Name字段
    email string    //未公開的email字段
}

如果在其它包中創(chuàng)建該類型,并賦值email字段,在定義階段是不會(huì)報(bào)錯(cuò)的,如下不會(huì)直接報(bào)錯(cuò)(identifier是包名):

ad := identifier.Admin{"admin", "admin@email.com"}

但是在運(yùn)行的時(shí)候,會(huì)報(bào)錯(cuò),錯(cuò)誤如下:

# command-line-arguments
.\main.go:43: implicit assignment of unexported field 'email' in identifier.Admin literal

7.3 訪問未公開的內(nèi)嵌對(duì)象中的公開字段

identifier包中定義如下結(jié)構(gòu)類型,apple是未公開的結(jié)構(gòu)類型,但是其Name字段是公開的。

type apple struct {
    Name string
}

//公開類型的Tree對(duì)象
type Tree struct {
    TreeName string //公開類型的TreeName
    apple           //未公開的apple類型
}

在其它包中,可以利用內(nèi)部對(duì)象屬性升級(jí)為外部對(duì)象的特性,為tree的未公開內(nèi)嵌類型apple的公開字段Name設(shè)值。

tree := identifier.Tree{TreeName: "MoneyTree"}
tree.Name = "apply"
fmt.Printf("%v \n", tree)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容