W3Cschool
恭喜您成為首批注冊(cè)用戶(hù)
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
原文鏈接:https://gopl-zh.github.io/ch6/ch6-06.html
一個(gè)對(duì)象的變量或者方法如果對(duì)調(diào)用方是不可見(jiàn)的話(huà),一般就被定義為“封裝”。封裝有時(shí)候也被叫做信息隱藏,同時(shí)也是面向?qū)ο缶幊套铌P(guān)鍵的一個(gè)方面。
Go語(yǔ)言只有一種控制可見(jiàn)性的手段:大寫(xiě)首字母的標(biāo)識(shí)符會(huì)從定義它們的包中被導(dǎo)出,小寫(xiě)字母的則不會(huì)。這種限制包內(nèi)成員的方式同樣適用于struct或者一個(gè)類(lèi)型的方法。因而如果我們想要封裝一個(gè)對(duì)象,我們必須將其定義為一個(gè)struct。
這也就是前面的小節(jié)中IntSet被定義為struct類(lèi)型的原因,盡管它只有一個(gè)字段:
type IntSet struct {
words []uint64
}
當(dāng)然,我們也可以把IntSet定義為一個(gè)slice類(lèi)型,但這樣我們就需要把代碼中所有方法里用到的s.words用*s
替換掉了:
type IntSet []uint64
盡管這個(gè)版本的IntSet在本質(zhì)上是一樣的,但它也允許其它包中可以直接讀取并編輯這個(gè)slice。換句話(huà)說(shuō),相對(duì)于*s
這個(gè)表達(dá)式會(huì)出現(xiàn)在所有的包中,s.words只需要在定義IntSet的包中出現(xiàn)(譯注:所以還是推薦后者吧的意思)。
這種基于名字的手段使得在語(yǔ)言中最小的封裝單元是package,而不是像其它語(yǔ)言一樣的類(lèi)型。一個(gè)struct類(lèi)型的字段對(duì)同一個(gè)包的所有代碼都有可見(jiàn)性,無(wú)論你的代碼是寫(xiě)在一個(gè)函數(shù)還是一個(gè)方法里。
封裝提供了三方面的優(yōu)點(diǎn)。首先,因?yàn)檎{(diào)用方不能直接修改對(duì)象的變量值,其只需要關(guān)注少量的語(yǔ)句并且只要弄懂少量變量的可能的值即可。
第二,隱藏實(shí)現(xiàn)的細(xì)節(jié),可以防止調(diào)用方依賴(lài)那些可能變化的具體實(shí)現(xiàn),這樣使設(shè)計(jì)包的程序員在不破壞對(duì)外的api情況下能得到更大的自由。
把bytes.Buffer這個(gè)類(lèi)型作為例子來(lái)考慮。這個(gè)類(lèi)型在做短字符串疊加的時(shí)候很常用,所以在設(shè)計(jì)的時(shí)候可以做一些預(yù)先的優(yōu)化,比如提前預(yù)留一部分空間,來(lái)避免反復(fù)的內(nèi)存分配。又因?yàn)锽uffer是一個(gè)struct類(lèi)型,這些額外的空間可以用附加的字節(jié)數(shù)組來(lái)保存,且放在一個(gè)小寫(xiě)字母開(kāi)頭的字段中。這樣在外部的調(diào)用方只能看到性能的提升,但并不會(huì)得到這個(gè)附加變量。Buffer和其增長(zhǎng)算法我們列在這里,為了簡(jiǎn)潔性稍微做了一些精簡(jiǎn):
type Buffer struct {
buf []byte
initial [64]byte
/* ... */
}
// Grow expands the buffer's capacity, if necessary,
// to guarantee space for another n bytes. [...]
func (b *Buffer) Grow(n int) {
if b.buf == nil {
b.buf = b.initial[:0] // use preallocated space initially
}
if len(b.buf)+n > cap(b.buf) {
buf := make([]byte, b.Len(), 2*cap(b.buf) + n)
copy(buf, b.buf)
b.buf = buf
}
}
封裝的第三個(gè)優(yōu)點(diǎn)也是最重要的優(yōu)點(diǎn),是阻止了外部調(diào)用方對(duì)對(duì)象內(nèi)部的值任意地進(jìn)行修改。因?yàn)閷?duì)象內(nèi)部變量只可以被同一個(gè)包內(nèi)的函數(shù)修改,所以包的作者可以讓這些函數(shù)確保對(duì)象內(nèi)部的一些值的不變性。比如下面的Counter類(lèi)型允許調(diào)用方來(lái)增加counter變量的值,并且允許將這個(gè)值reset為0,但是不允許隨便設(shè)置這個(gè)值(譯注:因?yàn)閴焊驮L(fǎng)問(wèn)不到):
type Counter struct { n int }
func (c *Counter) N() int { return c.n }
func (c *Counter) Increment() { c.n++ }
func (c *Counter) Reset() { c.n = 0 }
只用來(lái)訪(fǎng)問(wèn)或修改內(nèi)部變量的函數(shù)被稱(chēng)為setter或者getter,例子如下,比如log包里的Logger類(lèi)型對(duì)應(yīng)的一些函數(shù)。在命名一個(gè)getter方法時(shí),我們通常會(huì)省略掉前面的Get前綴。這種簡(jiǎn)潔上的偏好也可以推廣到各種類(lèi)型的前綴比如Fetch,F(xiàn)ind或者Lookup。
package log
type Logger struct {
flags int
prefix string
// ...
}
func (l *Logger) Flags() int
func (l *Logger) SetFlags(flag int)
func (l *Logger) Prefix() string
func (l *Logger) SetPrefix(prefix string)
Go的編碼風(fēng)格不禁止直接導(dǎo)出字段。當(dāng)然,一旦進(jìn)行了導(dǎo)出,就沒(méi)有辦法在保證API兼容的情況下去除對(duì)其的導(dǎo)出,所以在一開(kāi)始的選擇一定要經(jīng)過(guò)深思熟慮并且要考慮到包內(nèi)部的一些不變量的保證,未來(lái)可能的變化,以及調(diào)用方的代碼質(zhì)量是否會(huì)因?yàn)榘囊稽c(diǎn)修改而變差。
封裝并不總是理想的。 雖然封裝在有些情況是必要的,但有時(shí)候我們也需要暴露一些內(nèi)部?jī)?nèi)容,比如:time.Duration將其表現(xiàn)暴露為一個(gè)int64數(shù)字的納秒,使得我們可以用一般的數(shù)值操作來(lái)對(duì)時(shí)間進(jìn)行對(duì)比,甚至可以定義這種類(lèi)型的常量:
const day = 24 * time.Hour
fmt.Println(day.Seconds()) // "86400"
另一個(gè)例子,將IntSet和本章開(kāi)頭的geometry.Path進(jìn)行對(duì)比。Path被定義為一個(gè)slice類(lèi)型,這允許其調(diào)用slice的字面方法來(lái)對(duì)其內(nèi)部的points用range進(jìn)行迭代遍歷;在這一點(diǎn)上,IntSet是沒(méi)有辦法讓你這么做的。
這兩種類(lèi)型決定性的不同:geometry.Path的本質(zhì)是一個(gè)坐標(biāo)點(diǎn)的序列,不多也不少,我們可以預(yù)見(jiàn)到之后也并不會(huì)給他增加額外的字段,所以在geometry包中將Path暴露為一個(gè)slice。相比之下,IntSet僅僅是在這里用了一個(gè)[]uint64的slice。這個(gè)類(lèi)型還可以用[]uint類(lèi)型來(lái)表示,或者我們甚至可以用其它完全不同的占用更小內(nèi)存空間的東西來(lái)表示這個(gè)集合,所以我們可能還會(huì)需要額外的字段來(lái)在這個(gè)類(lèi)型中記錄元素的個(gè)數(shù)。也正是因?yàn)檫@些原因,我們讓IntSet對(duì)調(diào)用方不透明。
在這章中,我們學(xué)到了如何將方法與命名類(lèi)型進(jìn)行組合,并且知道了如何調(diào)用這些方法。盡管方法對(duì)于OOP編程來(lái)說(shuō)至關(guān)重要,但他們只是OOP編程里的半邊天。為了完成OOP,我們還需要接口。Go里的接口會(huì)在下一章中介紹。
![]() | ![]() |
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話(huà):173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: