go基礎-介面

来源:https://www.cnblogs.com/asdfzxv/archive/2023/08/03/17603442.html
-Advertisement-
Play Games

# Go 語言入門指南: 環境搭建、基礎語法和常用特性解析 | 青訓營 ## 從零開始 ### Go 語言簡介 ![img](https://img2023.cnblogs.com/blog/2724888/202308/2724888-20230803143447307-285055892.png ...


一、概述

介面是面向對象編程的重要概念,介面是對行為的抽象和概括,在主流面向對象語言Java、C++,介面和類之間有明確關係,稱為“實現介面”。這種關係一般會以“類派生圖”的方式進行,經常可以看到大型軟體極為複雜的派生樹,隨著系統的功能不斷增加,這棵“派生樹”會變得越來越複雜。

Go語言介面模型非常特別,就目前觀察是獨創。go介面設計是非侵入式,只要類型方法是介面方法的超集,那麼就認為類型實現了介面,兩者之間不需要顯示關聯,當然也沒有implements關鍵字,稱為隱式實現。相比Java、C++主流面向對象語言需要顯示實現介面,go的方式更加靈活、鬆散、耦合更低,當然也更加隱晦、代碼可讀性降低(當然有人不同意這種看法)。在不修改類型定義情況下,可以為其添加介面,這在Java、C++下是不可思議的。go的介面滿足鴨子模型,所謂鴨子類型:只要走起路來像鴨子、叫起來也像鴨子,那麼就可以把它當作鴨子。

 

二、基本使用

介面定義,描述一堆方法的集合,給出方法聲明即可,不能有預設實現,也不能有變數

type User interface {
    Say()
    GetName() string
}

定了一個user介面,包含兩個方法

 

任何類型都可以實現這兩個方法,不需要顯示使用implements關鍵字。滿足兩個條件,與介面方法簽名完全一致,是介面方法的超集。即可判定類型實現了介面。

type Sales struct {                    // 定義類型
    name string
}

func (p *Sales) GetName() string {    // 介面方法一
    return p.name
}

func (p *Sales) Say() {                // 介面方法二
    fmt.Println("Hi, I'm", p.name)
}

func (p *Sales) peddle() {            
    fmt.Printf("%s peddle", p.name)
}

Sales類型滿足兩個條件,可判斷實現了User介面。從代碼上看兩者沒有直接關聯,這就是隱式實現。

 

按照上面的兩個條件,Sales也實現瞭如下介面

type Person interface {
    GetName() string
}

可以看到非常鬆散,就是這麼簡潔。再次強調只要滿足兩個條件:與介面方法簽名一致,是介面方法的超集,即可判定類型實現了介面。從類型自身角度看,完全不知道自己實現了哪些介面。

 

通過實例調用方法

var sales Sales = Sales{name: "tom"}
sales.Say()

 

通過介面調用方法,只要類型實現了介面,就可以賦值給介面變數,並使用介面調用方法

var user User = &Sales{name: "tom"}        // 賦值給介面變數,註意是地址
user.Say()                                 // 通過介面調用方法

fmt.Printf("%T\n", user)                  // *main.Sales

介面是引用類型,和指針一樣,只能指向實例的地址。

 

介面主要目標是解耦,通常稱為面向介面編程,主流使用方式是函數形參是介面類型,調用時候傳遞介面變數,這也是介面存在的意義。

func PrintName(user User) {                    // 形參是User介面類型
    fmt.Println("姓名:", user.GetName())
}

var sales User = &Sales{name: "tom"}        
PrintName(sales)                            // 傳入user介面變數

形參是介面類型,可傳入所有實現了該介面的實例,不在依賴具體類型。

 

在標準庫中大量使用介面。比如排序是普片需求,標準庫提供了排序函數,形參是介面類型,任何實現了該介面的類型,都可直接使用排序函數

type Interface interface {        // 排序介面
    Len() int
    Less(i, j int) bool
    Swap(i, j int)
}

func Sort(data Interface) {        // 標準庫排序函數
    ...
}

 

和結構體一樣,介面也支持繼承特性

type User interface {
    Say()
    GetName() string
}

type Admin interface {
    User                // 繼承User介面
    TeamName string        // 自有屬性
}

需要實現包括繼承的所有方法,才判定實現了該介面

 

並非只能使用結構體實現介面,其他自定義類型也可以實現介面,如下X類型實現了Plus介面

type Plus interface {
    incr()
}

type X int

func (x *X) incr() {
    *x += 1
    fmt.Println(*x)
}

 

三、介面斷言

在介面變數上操作,用於檢查介面類型變數是否實現了期望的介面或者具體的類型。使用介面的本質,就是實例類型和介面類型之間轉換,而是否允許轉換就依賴斷言

value, ok := x.(T)

x 表示介面的類型,T 表示具體類型(也可以是介面),可根據該布爾值判斷 x 是否為 T 類型。

  • 如果 T 是實例類型,類型斷言會檢查 x 的動態類型是否滿足 T。如果成功返回 x 的動態值,其類型是 T。
  • 如果 T 是介面類型,類型斷言會檢查 x 的動態類型是否滿足 T。如果成功返回 值是 T 的介面值。
  • 無論 T 是什麼類型,如果 x 是 nil 介面值,類型斷言都會失敗。

 

使用上面案例進行斷言

var user User = &Sales{name: "tony"}

if value, ok := user.(User); ok {        // true,  介面斷言
    value.Say()    
}

_, ok = user.(*Sales)                    // true, 具體類型斷言, 註意這裡使用了指針類型

 

註意,如果不接收第二個返回值(也就是 ok),斷言失敗時會 panic,對nil斷言同樣也會 panic。

admin := user.(Admin)    // Admin是管理員介面,斷言失敗panic

 

具體類型實例如何斷言,可以先轉為為介面,然後再進行斷言

user1 := Sales{"tom"}

var data interface{} = user1      // 轉換為空介面

if _, ok := data.(Sales); ok {    // 再進行斷言
    fmt.Println("yes")
}

 

斷言常見使用場景,異常捕獲時判定錯誤類型

func ProtectRun(entry func()) {
    defer func() {
        err := recover()            // 獲取錯誤類型
        
        switch err.(type) {            // 斷言錯誤類型, 不同類型的錯誤採取不同的處理方式
        case runtime.Error: 
            fmt.Println("runtime error:", err)
        default:
            fmt.Println("error:", err)
        }
    }()

    ...
}

 

四、介面轉換

go語言基本數據類型轉換比較嚴格,所有基礎類型不支持隱式轉換,如下案例都不支持

s := "a" + 1    // err

// 不同長度的整型, 也支持自動轉換
var i int = 10
var n int8 = 20
m := i+n      // err

 

go只能顯示轉換

s := "a" + string(1)      // a1

var i int = 10
var n int8 = 20
m := i + int(n)            // 30

 

使用介面的本質就是類型轉換,賦值時轉換為介面變數,執行時候轉換為實例。 go 語言對於介面類型的轉換則非常的靈活,對象和介面之間的轉換、介面和介面之間的轉換都可能是隱式的轉換

var f *os.File
var a io.ReadCloser = f        // 隱式轉換, *os.File 滿足 io.ReadCloser 介面
var b io.Reader = a             // 隱式轉換, io.ReadCloser 滿足 io.Reader 介面
var c io.Closer = a             // 隱式轉換, io.ReadCloser 滿足 io.Closer 介面
var d io.Reader = a.(io.Reader) // 顯式轉換, io.Closer 不滿足 io.Reader 介面

 

有時候對象和介面之間太靈活了,導致需要人為地限制這種無意之間的適配。常見的做法是定義一個含特殊方法來區分介面。比如 runtime 包中的 Error 介面就定義了一個特有的 RuntimeError 方法,用於避免其它類型無意中適配了該介面

type runtime.Error interface {
    error
    RuntimeError()
}

 

不過這種做法只是君子協定,如果有人刻意偽造介面也是很容易的。再嚴格一點的做法是給介面定義一個私有方法。只有滿足了這個私有方法的對象才可能滿足這個介面,而私有方法的名字是包含包的絕對路徑名的,因此只能在包內部實現這個私有方法才能滿足這個介面。測試包中的 testing.TB 介面就是採用類似的技術

type testing.TB interface {
    Error(args ...interface{})
    Errorf(format string, args ...interface{})
    ...
    private()  // 私有方法
}

 

不過這種通過私有方法禁止外部對象實現介面的做法也是有代價的,首先是這個介面只能包內部使用,外部包正常情況下是無法直接創建滿足該介面對象的;其次,這種防護措施也不是絕對的,惡意的用戶依然可以繞過這種保護機制。通過嵌入匿名的 testing.TB 介面來偽造私有的 private 方法,因為介面方法是延遲綁定,編譯時 private 方法是否真的存在並不重要。

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 方法。

 

五、空介面

介面定義沒有聲明任何方法,稱為空介面,按照go規範任何類型都實現了空介面,因為都滿足了兩個實現條件。這就比較有意思了,空介面可以等於任何值,類似Java中的Object對象。

var data interface{}        // 定義空介面變數

data = 1
fmt.Printf("type=%T, value=%v\n", data, data)
data = "hello"
fmt.Printf("type=%T, value=%v\n", data, data

輸出如下

type=int, value=1
type=string, value=hello

 

函數形參是空介面類型,就表示可接收任何類型,然後再在斷言,不同的類型,採用不同的邏輯,在開發框架層時經常使用

func assertion(T interface{}) {
    switch T.(type) {
    case User:
        fmt.Println("user")
    case Admin:
        fmt.Println("admin")
    default:
        fmt.Println("default")
    }
}

T.(type)語法只能在switch中使用,可以理解定製語法糖,否則需要使用if逐個類型斷言

 

空介面在標準庫空也有普遍使用,比如panic函數終止程式時,可傳遞空介面類型的參數,捕獲錯誤時可獲取

type any = interface{}

func panic(v any)

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 問題代碼: 1 // 目標:使用Properties讀取屬性文件中的內容。 2 // 1、創建properties對象 3 Properties properties = new Properties(); 4 5 6 // 2、使用properties對象載入屬性文件中的鍵值對數據。 7 prop ...
  • # 職工管理系統 ## 1. 管理系統需求 職工管理系統可以用來管理公司內所有員工的信息,利用C++來實現一個基於多態的職工管理系統 公司中職工分為三類:普通員工、經理、老闆,顯示信息時,需要顯示職工編號、職工姓名、職工崗位、以及職責 + 普通員工職責:完成經理交給的任務 + 經理職責:完成老闆交給 ...
  • 哈嘍大家好,我是鹹魚 之前寫了個 shell 版本的 SSL 證書過期巡檢腳本 (文章:《[SSL 證書過期巡檢腳本](https://mp.weixin.qq.com/s?__biz=MzkzNzI1MzE2Mw==&mid=2247486153&idx=1&sn=52911a79b77c11d7 ...
  • 學習教程:【黑馬程式員2023新版JavaWeb開發教程,實現javaweb企業開發全流程(涵蓋Spring+MyBatis+SpringMVC+SpringBoot等)】 https://www.bilibili.com/video/BV1m84y1w7Tb/?p=161&share_source ...
  • Java記憶體區域包含程式計數器、虛擬機棧、本地方法棧、Java堆、方法區五個區域。 運行時數據區分類 Java記憶體區域 一、程式計數器 程式計數器(Program Counter Register)是一塊較小的記憶體空間,它可以看作是當前線程所執行的位元組碼的信號指示器。 位元組碼解釋器工作時就是通過改變 ...
  • 通常我們在做項目的時候,要手動搭建項目的結構,如controller,service,mapper,entity,是不是很麻煩,特別是資料庫表特別多時,現在介紹一下使用MybatisPlus時怎麼自動生成這些代碼。 1. 首先要在項目的pom.xml里引入必要的依賴,如下: ~~~xml com.b ...
  • ## Spring MVC Spring MVC是Spring框架的一部分,是一個Web應用程式框架。它旨在使用Model-View-Controller(MVC)設計模式輕鬆構建Web應用程式。 在Spring MVC中,應用程式被分為三個主要組件:Model、View和Controller。Mo ...
  • ## jsp ​ servlet 是無法將後端獲取的數據傳遞給html 頁面的,無法再servlet 中通過轉發或者是重定向的方式,給html 頁面傳遞響應的後端數據,servlet 中由於拼接過於繁瑣,是不適合寫html 的因此引入了 jsp ,既可以編寫 html標簽,也可以寫 Java 代碼, ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...