go語言介面避免無意被適配
學習記錄(Go語言高階程式設計)
物件和介面之間太靈活了,導致我們需要人為地限制這種無意之間的適配。常見的做法是定義一 個含特殊方法來區分介面。比如 runtime 包中的 Error 介面就定義了一個特有 的 RuntimeError 方法,用於避免其它型別無意中適配了該介面:
type runtime.Error interface {
error
RuntimeError()
}
在protobuf中, Message 介面也採用了類似的方法,也定義了一個特有的 ProtoMessage ,用於 避免其它型別無意中適配了該介面:
type proto.Message interface {
Reset()
String() string
ProtoMessage()
}
不過這種做法只是君子協定,如果有人刻意偽造一個 proto.Message 介面也是很容易的。再嚴格一 點的做法是給介面定義一個私有方法。只有滿足了這個私有方法的物件才可能滿足這個介面,而私有方法的名字是包含包的絕對路徑名的,因此只能在包內部實現這個私有方法才能滿足這個介面。測試包中 的 testing.TB 介面就是採用類似的技術:
type testing.TB interface {
Error(args ...interface{})
Errorf(format string, args ...interface{})
...
// A private method to prevent users implementing the
// interface and so future additions to it will not
// violate Go 1 compatibility.
private()
}
不過這種通過私有方法禁止外部物件實現介面的做法也是有代價的:首先是這個介面只能包內部使用, 外部包正常情況下是無法直接建立滿足該介面物件的;其次,這種防護措施也不是絕對的,惡意的使用者 依然可以繞過這種保護機制。
在前面的方法一節中我們講到,通過在結構體中嵌入匿名型別成員,可以繼承匿名型別的方法。其實這 個被嵌入的匿名成員不一定是普通型別,也可以是介面型別。我們可以通過嵌入匿名 的 testing.TB 介面來偽造私有的 private 方法,因為介面方法是延遲繫結,編譯 時 private 方法是否真的存在並不重要。
package main
import (
"fmt"
"testing"
)
type TB struct {
testing.TB
}
func (p *TB) Fatal(args ...interface{}) {
fmt.Println("TB.Fatal disabled!")
}
func main() {
var tb testing.TB = new(TB)
tb.Fatal("Hello, playground")
}
我們在自己的 TB 結構體型別中重新實現了 Fatal 方法,然後通過將物件隱式轉換 為 testing.TB 介面型別(因為內嵌了匿名的 testing.TB 物件,因此是滿足 testing.TB 介面 的),然後通過 testing.TB 介面來呼叫我們自己的 Fatal 方法
這種通過嵌入匿名介面或嵌入匿名指標物件來實現繼承的做法其實是一種純虛繼承,我們繼承的只是接 口指定的規範,真正的實現在執行的時候才被注入。比如,我們可以模擬實現一個gRPC的外掛:
type grpcPlugin struct {
*generator.Generator
}
func (p *grpcPlugin) Name() string {
return "grpc"
}
func (p *grpcPlugin) Init(g *generator.Generator) {
p.Generator = g
}
func (p *grpcPlugin) GenerateImports(file *generator.FileDescriptor) {
if len(file.Service) == 0 {
return
}
p.P(`import "google.golang.org/grpc"`)
// ...
}
構造的 grpcPlugin 型別物件必須滿足 generate.Plugin 介面 (在”github.com/golang/protobuf/protoc-gen-go/generator”包中)
type Plugin interface {
Name() string
Init(g *Generator)
Generate(file *FileDescriptor)
GenerateImports(file *FileDescriptor)
}
generate.Plugin 介面對應的 grpcPlugin 型別的 GenerateImports 方法中使用 的 p.P(…) 函式卻是通過 Init 函式注入的 generator.Generator 物件實現。這裡 的 generator.Generator 對應一個具體型別,但是如果 generator.Generator 是介面型別的話 我們甚至可以傳入直接的實現。
Go語言通過幾種簡單特性的組合,就輕易就實現了鴨子物件導向和虛擬繼承等高階特性,真的是不可思議
相關文章
- windows10:vscode下go語言的適配WindowsVSCodeGo
- go語言的介面Go
- go語言學習-介面Go
- Go 語言 nil 和介面Go
- Go語言適合做什麼 ?Go
- [Go語言寫介面]一、使用xcgui完成go語言第一個軟體介面GoGUI
- Go 語言介面詳解(一)Go
- Go 語言介面詳解(二)Go
- 呼叫後端介面 / 介面適配後端
- Go語言: 生成Protobuf的Service介面Go
- Android 國際化之多語言適配小記Android
- 《快學 Go 語言》第 9 課 —— 介面Go
- Go語言 如何配製 高效能sql.DBGoSQL
- Go語言適合用於哪些地方?Go能幹什麼?Go
- Go語言————1、初識GO語言Go
- go語言呼叫everything的SDK介面Go
- Go語言使用swagger生成介面文件GoSwagger
- Go 語言介面及使用介面實現連結串列插入Go
- cocos2d-x本地化/多語言適配
- GO語言Go
- Go語言學習筆記 - PART9 - 介面Go筆記
- Go語言學習之路-11-方法與介面Go
- 2020-10-18Go語言介面Go
- GO語言————2、GO語言環境安裝Go
- 非常適合GO語言新手學習的《Go語言從入門到實戰——簡明高效的Go語言實戰指南》課程——推薦分享Go
- Go語言之介面Go
- Go_go語言初探Go
- Go語言封裝、繼承、介面、多型和斷言的案例Go封裝繼承多型
- GOLANG介面適配,組合方式的靈活介面演化Golang
- 《Go 語言程式設計》讀書筆記(四)介面Go程式設計筆記
- go語言請求http介面示例 並解析jsonGoHTTPJSON
- 【Go語言入門系列】(八)Go語言是不是面嚮物件語言?Go物件
- go 語言常量Go
- Go語言mapGo
- go 語言切片Go
- go語言使用Go
- 非常適合小白入門的Go語言學習路線Go
- 介面卡模式-讓不相容的介面得以適配模式