使用Golang的interface介面設計原則
1 interface介面
interface 是GO語言的基礎特性之一。可以理解為一種型別的規範或者約定。它跟java,C# 不太一樣,不需要顯示說明實現了某個介面,它沒有繼承或子類或“implements”關鍵字,只是通過約定的形式,隱式的實現interface 中的方法即可。因此,Golang 中的 interface 讓編碼更靈活、易擴充套件。 如何理解go 語言中的interface ? 只需記住以下三點即可:
(1) interface 是方法宣告的集合 (2) 任何型別的物件實現了在interface 介面中宣告的全部方法,則表明該型別實現了該介面。 (3) interface 可以作為一種資料型別,實現了該介面的任何物件都可以給對應的介面型別變數賦值。
注意:
a. interface 可以被任意物件實現,一個型別/物件也可以實現多個 interface
b. 方法不能過載,如 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()方法,列印的效果不同,那麼就是體現出了多型的特性。
2 物件導向中的開閉原則
2.1 平鋪式的模組設計
那麼作為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的職責也不夠單一,程式碼的維護成本隨著業務的複雜正比成倍增大。
2.2 開閉原則設計
那麼,如果我們擁有介面, 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{})
}
再看開閉原則定義: 開閉原則:一個軟體實體如類、模組和函式應該對擴充套件開放,對修改關閉。 簡單的說就是在修改需求的時候,應該儘量通過擴充套件來實現變化,而不是通過修改已有程式碼來實現變化。
3 介面的意義
好了,現在interface已經基本瞭解,那麼介面的意義最終在哪裡呢,想必現在你已經有了一個初步的認知,實際上介面的最大的意義就是實現多型的思想,就是我們可以根據interface型別來設計API介面,那麼這種API介面的適應能力不僅能適應當下所實現的全部模組,也適應未來實現的模組來進行呼叫。 呼叫未來
可能就是介面的最大意義所在吧,這也是為什麼架構師那麼值錢,因為良好的架構師是可以針對interface設計一套框架,在未來許多年卻依然適用。
4 物件導向中的依賴倒轉原則
4.1 耦合度極高的模組關係設計
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)
}
我們來看上面的程式碼和圖中每個模組之間的依賴關係,實際上並沒有用到任何的interface
介面層的程式碼,顯然最後我們的兩個業務 張三開賓士
, 李四開寶馬
,程式中也都實現了。但是這種設計的問題就在於,小規模沒什麼問題,但是一旦程式需要擴充套件,比如我現在要增加一個豐田汽車
或者 司機王五
, 那麼模組和模組的依賴關係將成指數級遞增,想蜘蛛網一樣越來越難維護和捋順。
4.2 面向抽象層依賴倒轉
如上圖所示,如果我們在設計一個系統的時候,將模組分為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)
}
4.3 依賴倒轉小練習
模擬組裝2臺電腦, --- 抽象層 ---有顯示卡Card 方法display,有記憶體Memory 方法storage,有處理器CPU 方法calculate --- 實現層層 ---有 Intel因特爾公司 、產品有(顯示卡、記憶體、CPU),有 Kingston 公司, 產品有(記憶體3),有 NVIDIA 公司, 產品有(顯示卡) --- 邏輯層 ---1. 組裝一臺Intel系列的電腦,並執行,2. 組裝一臺 Intel CPU Kingston記憶體 NVIDIA顯示卡的電腦,並執行
/*
模擬組裝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()
}
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()
}
關於作者:
作者:Aceld(劉丹冰)
mail: danbing.at@gmail.com">danbing.at@gmail.com
github: https://github.com/aceld
原創書籍gitbook:
http://legacy.gitbook.com/@aceld
原創宣告:未經作者允許請勿轉載, 如果轉載請註明出處
相關文章
- 設計原則之【介面隔離原則】
- 設計原則:介面隔離原則(ISP)
- 軟體設計原則—介面隔離原則
- 必知必會的設計原則——介面隔離原則
- 設計模式:介面隔離原則設計模式
- 設計模式的七大原則(2) --介面隔離原則設計模式
- 移動使用者介面的5個設計原則
- Java中的介面與抽象類設計原則Java抽象
- 設計模式六大原則(四)----介面隔離原則設計模式
- Golang 學習——interface 介面學習(一)Golang
- Golang 學習——interface 介面學習(二)Golang
- 設計模式的設計原則設計模式
- 面象物件設計6大原則之四:介面隔離原則物件
- golang使用sqlx報錯:unsupported type []interface {}, a slice of interfaceGolangSQL
- 設計原則
- 設計原則:開閉原則(OCP)
- 【設計模式】設計原則設計模式
- 設計原則 設計模式設計模式
- 設計模式 - 設計原則設計模式
- java 設計模式6原則 介面,抽象類區別Java設計模式抽象
- 嘻哈說:設計模式之介面隔離原則設計模式
- [譯] 移動介面設計的 10 項啟發式原則
- 如何設計出“好看”的UI介面(一):排版的六項原則UI
- MySQL 索引的設計原則MySql索引
- HBase的RowKey設計原則
- 安全設計原則
- Hbase 設計原則
- SOLID 設計原則Solid
- URI設計原則
- 設計原則-依賴反轉原則
- Golang之interfaceGolang
- API(Application Programming Interface,應用程式程式設計介面)APIAPP程式設計
- 軟體設計原則—合成複用原則
- 設計原則之【裡式替換原則】
- 設計原則之【單一職責原則】
- 設計原則之【開放封閉原則】
- 設計原則之【依賴反轉原則】
- 【Golang】Go 通過結構(struct) 實現介面(interface)GolangStruct