Golang介面筆記(一)

Barney YU
9 min readNov 26, 2022

--

Golang 實際上並不是物件導向程式語言,但卻可以做到類似封裝、繼承、多型的功能,介面佔了很大的功勞,今天就來跟大家分享我整理介面的筆記

何謂介面?

  1. Go的介面是一種抽象型別
  2. 它是滿足隱含性的,無需聲明哪個struct實現了這個介面,e.g. 不像java一樣需要implement
  3. 可以用來定義方法,更精確的說是它的方法提供什麼行為
  4. 利用 interface 可實現泛型多型的功能,從而可以調用同一個函數名的函數但實現完全不同的功能。

介面基本用法

如何宣告

用type來宣告interface

裡面宣告抽象方法

type BankAccount interface {
GetName() string
GetBalance() int
Deposit(amount int)
Withdraw(amount int) error
}

可以看到BankAccount介面裏面宣告了三個方法,不過現在也只有定義並沒有實作

如何實作介面

Golang 中如果自定義型態實現了 interface 的所有方法,那麼它就會認定該自定義型態也是 interface 型態的一種

例如:在這邊宣告一個EsunAccount的struct,並且實踐了GetBalance() Deposit() Withdraw()這三個方法,那麼Golang就會自動判定我們實作了BankAccount這個介面

type EsunAccount struct {
}

//取得帳戶名稱
func (e *EsunAccount) GetName() string {
//TODO implement me
}

//取得帳戶餘額
func (e *EsunAccount) GetBalance() int {
//TODO implement me
}

//存錢
func (e *EsunAccount) Deposit(amount int) {
//TODO implement me
}

//領錢
func (e *EsunAccount) Withdraw(amount int) error {
//TODO implement me
}

特別注意的是:當我們實踐方法時,都是用指標(*EsunAccount)

這個所代表的意思是透過傳遞指標來操控同一個struct實例

那趕緊來看看介面的效益吧,實作功能

//esun.go
type EsunAccount struct {
balance int
}

func NewEsunAccount() *EsunAccount {
return &EsunAccount{
accountName: "esunAccount",
balance: 0,
}
}

func (e *EsunAccount) GetName() string {
fmt.Printf("Account Name= %s\n", e.accountName)
return e.accountName
}

func (e *EsunAccount) GetBalance() int {
return e.balance
}

func (e *EsunAccount) Deposit(amount int) {
e.balance += amount
}

func (e *EsunAccount) Withdraw(amount int) error {
newBalance := e.balance - amount
if newBalance < 0 {
return errors.New("insufficient funds")
}
e.balance = newBalance
return nil
}
//main.go
func main() {
esun := NewEsunAccount()
//存錢
esun.Deposit(300)
esun.Deposit(100)
esun.Deposit(500)

//領錢
esun.Withdraw(600)
//印出帳戶剩多少錢
fmt.Printf("%s balance: %d\n", esun.GetName(), esun.GetBalance())
}

執行結果:

esunAccount balance: 300

接下來要展現介面真正的力量了

當我們多一個銀行帳號CtbcAccount

//ctbc.go
type CtbcAccount struct {
balance int
fee int
}

func NewCtbcAccount() *CtbcAccount {
return &CtbcAccount{
accountName: "ctbcAccount",
balance: 0,
fee: 15,
}
}

func (c *CtbcAccount) GetName() string {
fmt.Printf("Account Name= %s\n", c.accountName)
return c.accountName
}

func (c *CtbcAccount) GetBalance() int {
return c.balance
}

func (c *CtbcAccount) Deposit(amount int) {
c.balance += amount
}

func (c *CtbcAccount) Withdraw(amount int) error {
newBalance := c.balance - amount - c.fee
if newBalance < 0 {
return errors.New("insufficient funds")
}
c.balance = newBalance
return nil
}

可以看到ctbc這銀行在領錢的時候會多一層手續費,多一個fee欄位,並在Withdraw()方法的實作內會多扣掉這個fee

讓我們回到main.go裡面去看,同樣對兩間銀行做存錢領錢的動作,帳戶會剩多少錢

//main.go
func main() {

myAccounts := []BankAccount{
NewEsunAccount(),
NewCtbcAccount(),
}

for _, account := range myAccounts {
//存錢
account.Deposit(100)
//領錢
if err := account.Withdraw(50); err != nil {
fmt.Printf("ERR: %d\n", err)
}
//印出帳戶剩多少錢
fmt.Printf("%s balance: %d\n", account.GetName(), account.GetBalance())
}
}

執行結果:

esunAccount balance: 50
ctbcAccount balance: 35

介面可以實現了多型的行為,參考以下做法,取得帳戶名稱

//main.go
func ShowAccountName(m BankAccount) {
m.GetName()
}

func main() {
var m BankAccount
esun := NewEsunAccount()
ctbc := NewCtbcAccount()
m = esun
ShowAccountName(m)
m = ctbc
ShowAccountName(m)
}

同一個方法,傳入指派不同實現介面的不同型別,就有不同的結果

執行結果:

Account Name= esunAccount
Account Name= ctbcAccount

空介面

Go程式的interface除了定義型態的行為,本身也是一種型態。而空介面則代表任意型態。

空介面宣告方式:

interface{}

例如下面的變數i的型態為empty interface,所以可以接收任意型態的值

type BankAccount struct {
accountName string
balance int
}

func printValueAndType(i interface{}) { // take empty interface as parameter
fmt.Printf("value=%v, type=%T\n", i, i)
}

func main() {
var i interface{} // declare var as empty interface type

i = "abc"
printValueAndType(i) // value=abc, type=string

i = 123
printValueAndType(i) // value=123, type=int

i = BankAccount{"ctbc", 1000}
printValueAndType(i) // value={ctbc 1000}, type=main.BankAccount
}

有沒有覺得空介面其實很像泛型(generics)

Go 1.18加入的泛型(generics)新增了一個關鍵字any作為空介面interface{}的別名,所以之後改用any即可

這個範例宣告一個map,而map的值可以是任何型態

m := make(map[int]any)
m["a"] = 123
m["b"] = "abc"

也可以看到最經典最常用標準函式庫的例子

fmt套件

func Println

func Println(a ...any) (n int, err error)

範例:

func main() {
fmt.Println(123) //123
fmt.Println("abc") //abc
fmt.Println(EsunAccount{"esun", 100}) //{esun 100}
}

所以其實平常用Println()印出什麼型別的東西都可以

那第一篇就先簡單的介紹到這,主要簡單介紹了什麼是介面跟介面的基本用法,大家也可以練習用看看!

下一篇會針對go常用套件庫搭配介面做更進階的介紹唷~

--

--