目錄
- 一、前言
- 二、開閉原則
- 三、依賴倒置原則
- 3.1、什么是依賴倒置原則
- 3.2、一個耦合度極高的模塊關系設計
- 3.3、面向抽象層依賴倒轉
一、前言
go的interface寫起來更自由, 無需顯示的實現, 只要實現了與interfece所包含的所有函數簽名的相同的方法即可。讓編碼更靈活, 易擴展。
如何理解go語言中的interface呢?
1. interface是方法聲明的集合
2.接口的方法與實現接口的類型方法格式一致
3.接口中所有方法均被實現
4. interface可以作為一種數據類型,實現了該接口的任何對象都可以給對應的接口類型變量賦值
特別說明兩點:
- interface 可以被任意對象實現,一個類型/對象也可以實現多個 interface
- 方法不能重載,如
eat(), eat(s string)
不能同時存在
那么作為interface
數據類型,他存在的意義在哪呢? 實際上是為了滿足一些面向對象的編程思想。我們知道,軟件設計的最高目標就是高內聚,低耦合
。那么其中有一個設計原則叫開閉原則
。什么是開閉原則
二、開閉原則
在面向對象編程領域中,開閉原則規定“軟件中的對象(類,模塊,函數等等)應該對于擴展是開放的,但是對于修改是封閉的”,這意味著一個實體是允許在不改變它的源代碼的前提下變更它的行為。
看重點: 對于擴展是開放的, 對于修改是封閉的.
舉個例子: 銀行每天要辦理不同的業務, 存款, 轉賬, 取款等. 如果直接是實體來實現如下
package bank
import "fmt"
type Banker struct {
}
func (b *Banker) Save() {
fmt.Println("存錢")
}
func (b *Banker) Transfer() {
fmt.Println("轉賬")
}
func (b *Banker) Get() {
fmt.Println("取錢")
}
有個人要來存錢取錢轉賬了
package main
import "aaa/bank"
func main() {
var b = bank.Banker{}
b.Save()
b.Get()
b.Transfer()
}
那么隨著業務越來越多, 越來越大. 我又要新增加一些業務, 比如基金, 股票. 然后越來越多,越來越大. 導致Banker這個模塊越來越臃腫

這樣的設計會導致,當我們去給Banker添加新的業務的時候,會直接修改原有的Banker代碼,那么Banker模塊的功能會越來越多,出現問題的幾率也就越來越大,假如此時Banker已經有99個業務了,現在我們要添加第100個業務,可能由于一次的不小心,導致之前99個業務也一起崩潰,因為所有的業務都在一個Banker類里,他們的耦合度太高,Banker的職責也不夠單一,代碼的維護成本隨著業務的復雜正比成倍增大。
我們使用開閉原則, 使用interface將banker模塊抽象出來. 然后根據這個抽象的模塊, 去實現save, get, transfer.....

那么依然可以搞定程序的需求。 然后,當我們想要給Banker添加額外功能的時候,之前我們是直接修改Banker的內容,現在我們可以單獨定義一個股票Banker(實現股票方法)
,到這個系統中。 而且股票Banker的實現成功或者失敗都不會影響之前的穩定系統,他很單一,而且獨立。
所以以上,當我們給一個系統添加一個功能的時候,不是通過修改代碼,而是通過增添代碼來完成,那么就是開閉原則的核心思想了。所以要想滿足上面的要求,是一定需要interface來提供一層抽象的接口的。
golang代碼實現如下:
package bank
import "fmt"
// 對銀行的業務進行抽象
type Business interface {
doBussiness()
}
// 存錢業務
type SaveBussiness struct {
}
func (b *SaveBussiness) doBussiness() {
fmt.Sprintf("存錢")
}
//取錢業務
type GetBussiness struct {
}
func (g *GetBussiness) doBussiness() {
fmt.Println("取錢")
}
// 轉賬業務
type TransferBusi struct {
}
func (t *TransferBusi) doBussiness() {
fmt.Sprintf("轉賬")
}
然后我今天去了銀行, 我們封裝一個銀行, 銀行有各種各樣的能力.
package main
import (
"aaa/bank"
"fmt"
)
// 這有一個銀行, 銀行可以辦理業務
func Bank(b bank.Business) {
fmt.Println("辦理業務: ", b.DoBussiness())
}
func main() {
// 辦理具體的業務
Bank(bank.SaveBussiness{})
Bank(bank.GetBussiness{})
Bank(bank.TransferBusi{})
}
這樣, 當銀行增加業務類型, 比如股票的時候, 只需要擴展業務接口就可以了, 不會對原來的接口進行修改
再看開閉原則定義:開閉原則:一個軟件實體如類、模塊和函數應該對擴展開放,對修改關閉。簡單的說就是在修改需求的時候,應該盡量通過擴展來實現變化,而不是通過修改已有代碼來實現變化。
接口的意義:
現在interface已經基本了解,那么接口的意義最終在哪里呢,想必現在你已經有了一個初步的認知,實際上接口的最大的意義就是實現多態的思想,就是我們可以根據interface類型來設計API接口,那么這種API接口的適應能力不僅能適應當下所實現的全部模塊,也適應未來實現的模塊來進行調用。 調用未來可能就是接口的最大意義所在吧,這也是為什么架構師那么值錢,因為良好的架構師是可以針對interface設計一套框架,在未來許多年卻依然適用。
三、依賴倒置原則
3.1、什么是依賴倒置原則
依賴倒置原則(Dependence Inversion Principle)是程序要依賴于抽象接口,不要依賴于具體實現。簡單的說就是要求對抽象進行編程,不要對實現進行編程,這樣就降低了客戶與實現模塊間的耦合。
3.2、一個耦合度極高的模塊關系設計

張三駕駛奔馳, 張三駕駛寶馬, 張三駕駛豐田.
李四駕駛寶馬, 李四駕駛奔馳, 李四駕駛豐田
package yldz
import "fmt"
// 奔馳車
type Benz struct {
}
func (b *Benz) run() string{
return fmt.Sprintf("奔馳啟動")
}
// 寶馬
type BM struct {
}
func (b *BM) run() string{
return fmt.Sprintf("寶馬啟動")
}
//豐田
type FT struct {
}
func (t *FT) run() string{
return fmt.Sprintf("豐田啟動")
}
//====駕車人,張三
type Zhangsan struct {
}
func (t *Zhangsan) DriverBenz(b *Benz) {
fmt.Println("張三駕駛", b.run())
}
func (t *Zhangsan) DriverBM(b *BM) {
fmt.Println("張三駕駛", b.run())
}
func (t *Zhangsan) DriverFT(b *FT) {
fmt.Println("張三駕駛", b.run())
}
// 駕車人----李四.......
package main
import "aaa/yldz"
func main() {
z := yldz.Zhangsan{}
z.DriverBenz(yldz.Benz{})
z.DriverBM(yldz.BM{})
z.DriverFT(yldz.FT{})
}
我們來看上面的代碼和圖中每個模塊之間的依賴關系,實際上并沒有用到任何的interface接口層的代碼,顯然最后我們的兩個業務 張三開奔馳, 李四開寶馬,程序中也都實現了。但是這種設計的問題就在于,小規模沒什么問題,但是一旦程序需要擴展,比如我現在要增加一個凱迪拉克汽車 或者 司機王五, 那么模塊和模塊的依賴關系將成指數級遞增,想蜘蛛網一樣越來越難維護和捋順。
3.3、面向抽象層依賴倒轉

如上圖所示,我們在設計一個系統的時候,將模塊分為3個層次,抽象層、實現層、業務邏輯層。
- 將抽象層的模塊和接口定義出來,這里就需要了
interface
接口的設計,
- 我們依照抽象層,依次實現每個實現層的模塊,在我們寫實現層代碼的時候,實際上我們只需要參考對應的抽象層實現就好了,實現每個模塊,也和其他的實現的模塊沒有關系,這樣也符合了上面介紹的開閉原則。這樣實現起來每個模塊只依賴對象的接口,而和其他模塊沒關系,依賴關系單一。系統容易擴展和維護。
- 業務邏輯層也是一樣,只需要參考抽象層的接口來實現業務就好了,抽象層暴露出來的接口就是我們業務層可以使用的方法,然后可以通過多態的方向,接口指針指向哪個實現模塊,調用了就是具體的實現方法,這樣我們業務邏輯層也是依賴抽象成編程。
看看具體的實現
package yldz
import "fmt"
type Car interface {
Run() string
}
type Driver interface {
// 接口變量肚子里有一個指針, 所以接口變量不需要使用指針.
Driver(car Car)
}
// 奔馳車
type Benz struct {
}
func (b *Benz) Run() string{
return fmt.Sprintf("奔馳啟動")
}
// 寶馬車
type BM struct {
}
func (b *BM) Run() string{
return fmt.Sprintf("寶馬啟動")
}
// 豐田車
type FT struct {
}
func (t *FT) Run() string{
return fmt.Sprintf("豐田啟動")
}
// ====張三
type Zhangsan struct {
}
func (t *Zhangsan) Driver(car Car) {
fmt.Println("駕駛",car.Run())
}
func main() {
benz := yldz.Benz{}
zs := yldz.Zhangsan{}
zs.Driver(benz)
ft := yldz.FT{}
zs.Driver(ft)
}
以上就是分析Go語言接口的設計原則的詳細內容,更多關于Go 接口的資料請關注腳本之家其它相關文章!
您可能感興趣的文章:- 一篇文章帶你玩轉go語言的接口
- Go語言-為什么返回值為接口類型,卻返回結構體
- go語言實現接口查詢
- GO語言gin框架實現管理員認證登陸接口
- Go語言使用swagger生成接口文檔的方法
- Go語言的接口詳解