一、 duck typing
duck typing意思是鴨子類型,我們把具備鴨子的行為等部分特征的一個東西叫做鴨子,這是鴨子類型的解釋。其實,在go語言中是采用鴨子類型這種思想來實現接口這種編程方式的,我們把一個類只要實現了某接口的方法,我們就說他是這個接口的實現類。如下:
我們定義了一個接口:
type duck interface {
Get(s string) string //不用func修飾這個方法
}
下面寫一個類:
type TheClass struct {
S string
}
這個類實現了上面接口中的方法,如下,並不需要聲明我們要實現某個接口。
func (tc TheClass) Get(s string) string {
return tc.S;
}
實現類前面的類型是屬於值類型傳遞,因為我們的接口里面實際是包了一個類型信息與及指針,所以實現一般采用直接的值傳遞而不是指針傳遞,因為一樣可以對內容修改。
如上通過go語言的方式來說明了什么是鴨子類型。
二、 go語言中的接口。
上面講過了接口的實現方式。
type duck interface {
Get(s string) string
}
之前我們有講過兩種go語言的兩種“繼承“方式,一種是類似於裝飾的叫做組合,在類里面放”父類“,另一種是取別名,當然取別名算不上,他不可以增加成員變量。那么接口可以”繼承“嗎?嗯,當然可以,如下
type littleDuck interface {
duck
A() string
}
這樣應該叫接口的“繼承”
剛剛在golang社區看到一篇被噴的文章,下面有個人這樣說:“golang的接口是非入侵式的,樓主讓非得把(**)接口寫入到(**)結構中,真是人才啊“
不是很理解什么意思,難道不能夠用接口繼承嗎?問題先留在這里,以后再解決。
(n分鍾過后)
查了下什么叫入侵式與非入侵式,附上自己的理解:入侵式是指要申明接口實現了某一個接口什么的,但是非入侵式就不用,一般的oo語言例如java就是入侵式接口設計,需要說明實現,有層級,但是go語言提倡是非入侵式,相對入侵式可能更靈活一點,不那么多依賴,但是這兩種設計都各有優點,什么優點?看完下面的就知道了,接下來用例子來說下自己的理解吧。
à:
Bird 接口 : fly(),run();
Chicken 接口 : run()
Wild 接口 :fly()
如上3個接口,在入侵式接口設計中,chicken接口與wild接口需要去繼承bird接口,因為他們屬於這個大類,那么我們創建一個chicken的實現類對象的話,只需要實現chicken,然后wild同理。這樣做就是入侵式接口設計的思路。
在java中像這樣:
interface Bird{
void run();
void fly();
}
interface Wild extends Bird{
void run();
}
interface Chicken extends Bird{
void fly();
}
如果我們想要創建一個Wild的類型的類那么就需要創建一個,需要給wild寫個實現類,一個Chicken類型的類就需要寫一個Chicken實現類,Bird就需要寫一個Bird的實現類,顯然接口的復用性不高。當然這樣的類型是相當清晰的。
同樣有另外一種寫法,這樣算是入侵式設計的思想,如下:
interface Run{
void run();
}
interface Fly{
void fly();
}
interface Bird1 extends Run,Fly{}
java的接口繼承是支持多繼承的
這樣的寫法是一種組裝的思想,這也是oo語言中的入侵式接口設計思路,通過繼承的方式組裝好,然后去寫Bird1的實現類,沒什么問題。
再看看非入侵式:go語言推薦使用非入侵式接口設計,在寫實現類的時候不需要說明實現哪個接口,也不需要去思考這是哪個接口的實現類,只要實現相應的方法就行,一般推薦這樣寫:
type chicken interface {
run()
}
type wild interface {
fly()
}
type BirdImpl struct {
}
func (b BirdImpl) run() {
}
func (b BirdImpl) fly(){
}
如上的方式很靈活,我們直接可以birdImpl創建一個類,然后實現一個方法他就屬於某一個類型,不用去組裝接口。當然,我們也可以通過實現接口的方式來實現接口,如:
type Bird interface {
run()
fly()
}
type chicken interface {
run()
}
type wild interface {
fly()
}
這樣的方式相當於bird實現了兩個接口的接口,我要創建實體對象的時候需要再創建一個類:
type BirdImpl struct {}
func (b BirdImpl) run(){}
func (b BirdImpl) fly(){}
這個類從這里看必定就實現了上述的3個接口,這樣的寫法其實是可以的,但是看起來冗余了許多,我完全可以不需要Bird 或者另外兩個接口。Go語言這樣的接口設計方式相對更簡單、靈活了。當然為了更具解釋性,我們可以把wild名改成Fly,chicken改成Run。當然這樣的組合方式在go語言使用還是不少,因為通過組合的方式也很方便靈活
那么我們可以這樣來設計go語言的接口:
type Bird interface {
run()
fly()
}
type BirdImpl struct {}
func (b BirdImpl) run() {}
func (b BirdImpl) fly(){}
或者這樣:
type Run interface {
run()
}
type Fly interface {
fly()
}
type BirdImpl struct {}
func (b BirdImpl) run() {}
func (b BirdImpl) fly(){}
這樣就是非入侵式接口設計,不在接口使用繼承。好像最后總結下愛就這么一句話,不在接口關系中使用繼承與及接口實現。
剛剛留了一個問題,入侵式與非入侵式的比較,從上面也可以看到入侵式接口設計類別層級什么的十分清晰,沒有那么多依賴,解釋性也比較好。非入侵式呢?相對比較靈活,簡單,接口復用率高。(這都是我的理解哈,不代表官方說法)
網上對入侵式與非入侵式的觀點很多,以上只是我的理解,而且go語言的源碼里面也有很多組合接口的地方,我也很難說,這兩個概念就先放一放。
三、 接口的使用。
首先說下接口變量的結構,其內部有兩個東西,一個是接口實現者的類型,一個是接口實現者的指針,我們來看一段段代碼:
var o A
o = class.B{}
fmt.Printf("%T,%p",o,o)
這里的B實現了接口A,T是類型,v是值,可以知道o里面實際含有兩個人東西。同時要提下,如果在不同包下進行接口實現,記住大寫方法名首字母,這樣才是public的。
接下來我們講下接口的組合,上面講到接口的組合不被推薦,說法是有問題的,通過后面的學習,接口是可以被組合的,而且在go的源碼中還大量被用到,這種組合的方式是很方便的,當然存在的問題也是問題,這個就不去爭議了,網上觀點不一。
接口的組合上文已經講過了,這里我們來講講interface{},interface{}代表go語言里面的所有類型,意義和java的Object一樣,但是go不是說所有類都繼承interface,沒有這種說法:
i := [...]interface{}{1,"2",3,"a","v"}
fmt.Println(i)
如上代碼,數組元素可以是任意類型,當然可以作為函數傳入值類型的時候,就表示可以傳入任意類型。
四、 Go兩種常用接口。
a) stringer
這個接口相當與java的toString()方法,我們只要實現string()string方法,就算是實現了這個接口,直接打印對象的時候自動調用string。
func (b B) String() string {
return "實現了stringer的B"
}
比如這樣寫。
b) reader/writer
file其實是實現了reader與writer兩個接口的,一般我們需要reader或者writer實例的時候可以傳一個file進去也是一樣的 ,這里可以用組合
type file interface{
Reader
Writer
}