inteface接口
interface 是GO語言的基礎特性之一。可以理解爲一種類型的規範或者約定。它跟java,C# 不太一樣,不需要顯示說明實現了某個接口,它沒有繼承或子類或“implements”關鍵字,只是通過約定的形式,隱式的實現interface 中的方法即可。因此,Golang 中的 interface 讓編碼更靈活、易擴展。
如何理解go 語言中的interface ?只需記住以下三點即可。
1、interface是方法聲明的集合
2、任何類型的對象實現了在interface接口中聲明的全部方法,則表明該類型實現了接口。
3、interface可以作爲一種數據類型,實現了該接口的任何對象都可以給對應的接口類型變量賦值。
interface 與 對象的 對應關係:interface可以被任意對象實現,一個類型/對象也可以實現多個interface.
Interface(接口)的意義:
實際上接口的最大的意義就是實現多態的思想, 就是我們可以根據interface類型來設計API接口,那麼這種API接口的適應能力不僅能適應當下所實現的全部模塊,也適應未來實現的模塊來進行調用。 調用未來可能就是接口的最大意義所在吧,這也是爲什麼架構師那麼值錢,因爲良好的架構師是可以針對interface設計一套框架,在未來許多年卻依然適用。
Interface ,在 封裝,實現(implements: 其他語言叫繼承),多態。
例子
package main
import "fmt"
type Phone interface {
call()
}
type NokiaPhone struct {
}
func (nokiaPhone NokiaPhone) call() {
fmt.Println("I am Nokia, I can call you!")
}
type ApplePhone struct {
}
func (iPhone ApplePhone) call() {
fmt.Println("I am Apple Phone, I can call you!")
}
func main() {
var phone Phone
phone = new(NokiaPhone)
phone.call()
phone = new(ApplePhone)
phone.call()
}
/**
多個對象 實現 接口,也體現了 多態 特性,同樣的一個接口類型 變量 ,分別被賦予(指向)不同的實體對象,調用call()方法,執行的結果不同,
這就體現出了 多態的特性。 也可以說 體現了 封裝(struct來體現) ,實現(implements), 多態
*/
面向對象中的依賴倒轉原則 : 一句話 業務邏輯層 ,實現層 要依賴 抽象,而不是依賴細節!。
一些原則 是 從戰略上 對業務進行抽象。
如何 對業務進行抽象出 一些 接口 (php語言中可以是 接口或抽象類)呢?來看一個例子:
業務層, 抽象層,實現層 。這個封層思想/方法 很好。一般程序員 可能是 業務層(需求)來了,想着 快速的實現(實現層),沒有仔細地思考抽象層);更好的方式是 :業務層(需求) ,再抽象層,再實現層,再業務層。這個思考方式可以一開始就這樣做,或者放在後面代碼優化的時候再這樣做。(因爲並不是所有的需求都能很好的去抽象出來一些公共的東西,要看具體的業務是什麼)
從 張三開奔馳,李四開寶馬 到 司機開車的抽象(抽象層); 張三,李四 實現司機;奔馳,寶馬實現車; 最後實現 張三開奔馳,李四開寶馬的業務需求(業務邏輯層)
代碼如下:
package main
import "fmt"
// ===== > 抽象層 < ========
type Car interface {
Run()
}
type Driver interface {
Drive(car Car)
}
// ===== > 實現層 < ========
type BenZ struct {
//...
}
func (benz * BenZ) Run() {
fmt.Println("Benz is running...")
}
type Bmw struct {
//...
}
func (bmw * Bmw) Run() {
fmt.Println("Bmw is running...")
}
type Zhang_3 struct {
//...
}
func (zhang3 *Zhang_3) Drive(car Car) {
fmt.Println("Zhang3 drive car")
car.Run()
}
type Li_4 struct {
//...
}
func (li4 *Li_4) Drive(car Car) {
fmt.Println("li4 drive car")
car.Run()
}
// ===== > 業務邏輯層 < ========
func main() {
//張3 開 寶馬
var bmw Car
bmw = &Bmw{}
var zhang3 Driver
zhang3 = &Zhang_3{}
zhang3.Drive(bmw)
//李4 開 奔馳
var benz Car
benz = &BenZ{}
var li4 Driver
li4 = &Li_4{}
li4.Drive(benz)
}
另一個 抽象的例子: 組裝電腦。
模擬組裝2臺電腦
--- 抽象層 ---
有顯卡Card 方法display,有內存Memory 方法storage,有處理器CPU 方法calculate
--- 實現層 ---
有 Intel因特爾公司 、產品有(顯卡、內存、CPU),有 Kingston 公司, 產品有(內存3),有 NVIDIA 公司, 產品有(顯卡)
--- 邏輯層 ---
1. 組裝一臺Intel系列的電腦,並運行 2. 組裝一臺 Intel CPU Kingston內存 NVIDIA顯卡的電腦,並運行
代碼如下:
package main
import "fmt"
//------ 抽象層 -----
type Card interface{
Display()
}
type Memory interface {
Storage()
/**
模擬了一種情景,接口層寫好了,實現層寫好了,業務層寫好了。代碼完美運行了一段時間。某天某個人對接口層加上了一個方法!
這時可能會出現什麼情況呢?
1. 程序報錯了,報類型不對的錯誤! 原因是 子類(實現層的struct /對象)沒有實現這個方法,接口與這個 struct對象(php類比子類)無任何關係了。
2。程序依然正常的運行着。 爲什麼呢?很可能是 struct對象 顯示地 繼承接口 的寫法。
總結 看來顯示地 實現接口,也是有着一定好處的!同樣也可能意味着接口着很多或更多的功能(方法)你子類(struct對象)並沒有使用(實現)!!
本人目前覺得 顯式地 繼承/實現 好一些,壞處有哪些?尚不清楚!
與PHP implements/extends 比較 Go 的interface 似乎更加靈活,像php子類不實現接口中的所有方法就會報錯!!,而Go不會!
顯示地 實現接口還有一個好處,就是我不用編輯器提示,我就知道這個struct 實現了該接口!這對大型工程,或者不使用編輯提示功能情況下,顯得尤其
方便和重要!
*/
Card
}
type CPU interface {
Calculate()
}
type Computer struct {
cpu CPU
mem Memory
card Card
}
func (computer *Computer) DoWork() {
computer.cpu.Calculate()
computer.mem.Storage()
computer.card.Display()
}
func NewComputer(cpu CPU, mem Memory, card Card) *Computer{
return &Computer{
cpu:cpu,
mem:mem,
card:card,
}
}
//------ 實現層 -----
//intel
type IntelCPU struct {
//CPU //這裏你可能有疑問,只要實現Calculate() 方法就可以被認爲是 CPU 了,然而寫了也沒錯,因爲抽象層和實現層都是你一個人
//寫的,你很清楚你寫的IntelCPU 就是一款CPU ,相當於其他語言如PHP 顯式地 extends 或 implements
}
func (this *IntelCPU) Calculate() {
fmt.Println("Intel CPU 開始計算了...")
}
type IntelMemory struct {
Memory //這裏你可能有疑問,只要實現Storage() 方法就可以被認爲是 Memory 了,然而寫了也沒錯,因爲抽象層和實現層都是你一個人
//寫的,你很清楚你寫的IntelMemory 就是一款Memory ,相當於其他語言如PHP 顯式地 extends 或 implements; 這樣還有一個好處就是不管以後接口(抽象層)
//又增加了一個方法(你不知道,抽象層也沒有義務非要告訴你),這時候顯式好處是 程序依然認爲你的 實現層IntelMemory 依然實現了Memory接口。
//儘管你並沒有實現它(interface接口)的新增的那個方法!
}
func (this *IntelMemory) Storage() {
fmt.Println("Intel Memory 開始存儲了...")
}
type IntelCard struct {
Card
}
func (this *IntelCard) Display() {
fmt.Println("Intel Card 開始顯示了...")
}
//kingston
type KingstonMemory struct {
Memory
}
func (this *KingstonMemory) Storage() {
fmt.Println("Kingston memory storage...")
}
//nvidia
type NvidiaCard struct {
Card
}
func (this *NvidiaCard) Display() {
fmt.Println("Nvidia card display...")
}
//------ 業務邏輯層 -----
func main() {
//intel系列的電腦
com1 := NewComputer(&IntelCPU{}, &IntelMemory{}, &IntelCard{})
com1.DoWork()
//雜牌子
com2 := NewComputer(&IntelCPU{}, &KingstonMemory{}, &NvidiaCard{})
com2.DoWork()
}
總結 看來顯示地 實現接口,也是有着一定好處的!
1.這樣還有一個好處就是不管以後接口(抽象層)又增加了一個方法(你不知道,抽象層也沒有義務非要告訴你),這時候顯式好處是 程序依然認爲你的 實現層IntelMemory 依然實現了Memory接口。儘管你並沒有實現它(interface接口)的新增的那個方法! 同樣也可能意味着接口着很多或更多的功能(方法)你子類(struct對象)並沒有使用(實現)!!
2.顯示地 實現接口還有一個好處,就是我不用編輯器提示,我就知道這個struct 實現了該接口!這對大型工程,或者不使用編輯提示功能情況下,顯得尤其 方便和重要!
Go 是面向對象的編程語言嗎?
官方FAQ給出了標準答案: Yes and No.
還有一種“模擬”產生子類的方法,就是通過在類型中嵌入其它的類型,但是這是一種“組合”的方式,而不是繼承。
嵌入深度。如果沒有嵌入,我們定義嵌入深度爲0,如果有嵌入,並且嵌入的接口沒有嵌入的話,我們稱之爲深度爲1
,以此類推。比如下面接口A
的嵌入深度爲2
:
type A interface {
B // 嵌入
}
type B interface {
C
}
type C interface {
}
【標準庫中接口中嵌入接口的數量】: 絕大部分的接口(131 個)都不會嵌入其它接口的,嵌入最多的是mime/multipart.File[2],嵌入了四個接口:
其它 6 個項目(Docker、etcd、grpc-go、prometheus、consul、influxdb )使用接口的情況:可以看到同樣大部分接口嵌入數量都在 0 個或者 1 個,嵌入最多的是 kubernetes 的CoreV1Interface[3]接口,嵌入了 16 個接口,可以說是一個巨無霸嵌入接口了:
【嵌入的接口深度】:準庫中不使用太深的嵌入方式,比較多的也就是嵌入一次。
精選項目中使用接口的方式也一樣,很少使用嵌入深度很長的方式,最長也就是 2,而且只有兩個接口:kubernetes/.../Stream[4]和moby/.../WriteCommitCloser[5],而且主要是因爲嵌入io.ReadWriteCloser
和io.WriteCloser
導致。
【接口中直接定義的方法的數量】:
準庫中接口定義的直接方法的數量都很少,不會設置很多的方法,接口都比較精巧幹練(7個以內),比較特殊的是reflect/Type[6]接口,定義了 31 個直接方法。
同樣,精選項目中的接口直接定義的方法也比較少,超過 10 個方法的接口少之又少,最多的是influxdata/.../TSMFile[7]接口,定義了足足 42 個方法。
【接口中總的方法的數量】:
標準庫中 1 接口的總方法數量基本都在 8 個以下。
精選項目中接口定義的總方法基本都在 12 個以下。