interface接口
interface 是GO語言的基礎特性之一。可以理解爲一種類型的規範或者約定。它跟java,C# 不太一樣,不需要顯示說明實現了某個接口,它沒有繼承或子類或“implements”關鍵字,只是通過約定的形式,隱式的實現interface 中的方法即可。因此,Golang 中的 interface 讓編碼更靈活、易擴展。
如何理解go 語言中的interface ?只需記住以下三點即可。
- interface是方法聲明的集合
- 任何類型的對象實現了在interface接口中聲明的全部方法,則表明該類型實現了接口。
- interface可以作爲一種數據類型,實現了該接口的任何對象都可以給對應的接口類型變量賦值。
- interface可以被任意對象實現,一個類型/對象也可以實現多個interface.
- 方法不能重載,如eat(), eat(s string)不能同時存在
示例代碼
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()
}
上述中體現了 interface 接口的語法,在 main 函數中,也體現了 多態 的特性。
同樣一個 phone 的抽象接口,分別指向不同的實體對象,調用的call()方法,打印的效果不同,那麼就是體現出了多態的特性。
面向對象中的開閉原則
平鋪式的模塊設計
那麼作爲 interface 數據類型,他存在的意義在哪呢?實際上是爲了滿足一些面向對象的編程思想。我們知道,軟件設計的最高目標就是 高內聚,低耦合 。那麼其中有一個設計原則叫 開閉原則 。什麼是開閉原則呢,接下來我們看一個例子:
package main
import "fmt"
//我們要寫一個類,Banker銀行業務員
type Banker struct {
}
//存款業務
func (this *Banker) Save() {
fmt.Println( "進行了 存款業務...")
}
//轉賬業務
func (this *Banker) Transfer() {
fmt.Println( "進行了 轉賬業務...")
}
//支付業務
func (this *Banker) Pay() {
fmt.Println( "進行了 支付業務...")
}
func main() {
banker := &Banker{}
banker.Save()
banker.Transfer()
banker.Pay()
}
代碼很簡單,就是一個銀行業務員,他可能擁有很多的業務,比如Save()存款、Transfer()轉賬、Pay()支付等。那麼如果這個業務員模塊只有這幾個方法還好,但是隨着我們的程序寫的越來越複雜,銀行業務員可能就要增加方法,會導致業務員模塊越來越臃腫。
這樣的設計會導致,當我們去給Banker添加新的業務的時候,會直接修改原有的Banker代碼,那麼Banker模塊的功能會越來越多,出現問題的機率也就越來越大,假如此時Banker已經有99個業務了,現在我們要添加第100個業務,可能由於一次的不小心,導致之前99個業務也一起崩潰,因爲所有的業務都在一個Banker類裏,他們的耦合度太高,Banker的職責也不夠單一,代碼的維護成本隨着業務的複雜正比成倍增大。
開閉設計原則
那麼,如果我們擁有接口, interface這個東西,那麼我們就可以抽象一層出來,製作一個抽象的Banker模塊,然後提供一個抽象的方法。分別根據這個抽象模塊,去實現支付Banker(實現支付方法),轉賬Banker(實現轉賬方法)
如下:
那麼依然可以搞定程序的需求。然後,當我們想要給Banker添加額外功能的時候,之前我們是直接修改Banker的內容,現在我們可以單獨定義一個股票Banker(實現股票方法),到這個系統中。而且股票Banker的實現成功或者失敗都不會影響之前的穩定系統,他很單一,而且獨立。
所以以上,當我們給一個系統添加一個功能的時候,不是通過修改代碼,而是通過增添代碼來完成,那麼就是開閉原則的核心思想了。所以要想滿足上面的要求,是一定需要interface來提供一層抽象的接口的。
golang代碼實現如下:
package main
import "fmt"
//抽象的銀行業務員
type AbstractBanker interface{
DoBusi() //抽象的處理業務接口
}
//存款的業務員
type SaveBanker struct {
//AbstractBanker
}
func (sb *SaveBanker) DoBusi() {
fmt.Println("進行了存款")
}
//轉賬的業務員
type TransferBanker struct {
//AbstractBanker
}
func (tb *TransferBanker) DoBusi() {
fmt.Println("進行了轉賬")
}
//支付的業務員
type PayBanker struct {
//AbstractBanker
}
func (pb *PayBanker) DoBusi() {
fmt.Println("進行了支付")
}
func main() {
//進行存款
sb := &SaveBanker{}
sb.DoBusi()
//進行轉賬
tb := &TransferBanker{}
tb.DoBusi()
//進行支付
pb := &PayBanker{}
pb.DoBusi()
}
當然我們也可以根據AbstractBanker設計一個小框架
//實現架構層(基於抽象層進行業務封裝-針對interface接口進行封裝)
func BankerBusiness(banker AbstractBanker) {
//通過接口來向下調用,(多態現象)
banker.DoBusi()
}
那麼main中可以如下實現業務調用:
func main() {
//進行存款
BankerBusiness(&SaveBanker{})
//進行存款
BankerBusiness(&TransferBanker{})
//進行存款
BankerBusiness(&PayBanker{})
}
開閉原則定義:
一個軟件實體如類、模塊和函數應該對擴展開放,對修改關閉。
簡單的說就是在修改需求的時候,應該儘量通過擴展來實現變化,而不是通過修改已有代碼來實現變化。
接口的意義
好了,現在interface已經基本瞭解,那麼接口的意義最終在哪裏呢,想必現在你已經有了一個初步的認知,實際上接口的最大的意義就是實現多態的思想,就是我們可以根據interface類型來設計API接口,那麼這種API接口的適應能力不僅能適應當下所實現的全部模塊,也適應未來實現的模塊來進行調用。 調用未來可能就是接口的最大意義所在吧,這也是爲什麼架構師那麼值錢,因爲良好的架構師是可以針對interface設計一套框架,在未來許多年卻依然適用。
面向對象中的依賴倒轉原則
耦合度極高的模塊設計
package main
import "fmt"
// === > 奔馳汽車 <===
type Benz struct {
//...
}
func (this *Benz) Run() {
fmt.Println("Benz is running...")
}
// === > 寶馬汽車 <===
type BMW struct {
//...
}
func (this *BMW) Run() {
fmt.Println("BMW is running ...")
}
//===> 司機張三 <===
type Zhang3 struct {
//...
}
func (zhang3 *Zhang3) DriveBenZ(benz *Benz) {
fmt.Println("zhang3 Drive Benz")
benz.Run()
}
func (zhang3 *Zhang3) DriveBMW(bmw *BMW) {
fmt.Println("zhang3 drive BMW")
bmw.Run()
}
//===> 司機李四 <===
type Li4 struct {
//...
}
func (li4 *Li4) DriveBenZ(benz *Benz) {
fmt.Println("li4 Drive Benz")
benz.Run()
}
func (li4 *Li4) DriveBMW(bmw *BMW) {
fmt.Println("li4 drive BMW")
bmw.Run()
}
func main() {
//業務1 張3開奔馳
benz := &Benz{}
zhang3 := &Zhang3{}
zhang3.DriveBenZ(benz)
//業務2 李四開寶馬
bmw := &BMW{}
li4 := &Li4{}
li4.DriveBMW(bmw)
}
我們來看上面的代碼和圖中每個模塊之間的依賴關係,實際上並沒有用到任何的
接口層的代碼,顯然最後我們的兩個業務,程序中也都實現了。但是這種設計的問題就在於,小規模沒什麼問題,但是一旦程序需要擴展,比如我現在要增加一個 或者 , 那麼模塊和模塊的依賴關係將成指數級遞增,想蜘蛛網一樣越來越難維護和捋順。
面向抽象層的依賴倒轉設計
如上圖所示,如果我們在設計一個系統的時候,將模塊分爲3個層次,抽象層、實現層、業務邏輯層。那麼,我們首先將抽象層的模塊和接口定義出來,這裏就需要了interface接口的設計,然後我們依照抽象層,依次實現每個實現層的模塊,在我們寫實現層代碼的時候,實際上我們只需要參考對應的抽象層實現就好了,實現每個模塊,也和其他的實現的模塊沒有關係,這樣也符合了上面介紹的開閉原則。這樣實現起來每個模塊只依賴對象的接口,而和其他模塊沒關係,依賴關係單一。系統容易擴展和維護。
我們在指定業務邏輯也是一樣,只需要參考抽象層的接口來業務就好了,抽象層暴露出來的接口就是我們業務層可以使用的方法,然後可以通過多態的線下,接口指針指向哪個實現模塊,調用了就是具體的實現方法,這樣我們業務邏輯層也是依賴抽象成編程。
我們就將這種的設計原則叫做依賴倒轉原則
來一起看一下修改的代碼:
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 公司, 產品有(顯卡) -
邏輯層
組裝一臺Intel系列的電腦,並運行 2. 組裝一臺 Intel CPU Kingston內存 NVIDIA顯卡的電腦,並運行
package main
import "fmt"
//------ 抽象層 -----
type Card interface{
Display()
}
type Memory interface {
Storage()
}
type CPU interface {
Calculate()
}
type Computer struct {
cpu CPU
mem Memory
card Card
}
func NewComputer(cpu CPU, mem Memory, card Card) *Computer{
return &Computer{
cpu:cpu,
mem:mem,
card:card,
}
}
func (this *Computer) DoWork() {
this.cpu.Calculate()
this.mem.Storage()
this.card.Display()
}
//------ 實現層 -----
//intel
type IntelCPU struct {
CPU
}
func (this *IntelCPU) Calculate() {
fmt.Println("Intel CPU 開始計算了...")
}
type IntelMemory struct {
Memory
}
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()
}