iT邦幫忙

2023 iThome 鐵人賽

DAY 18
0
自我挑戰組

Go in 3o系列 第 18

[Day18] Go in 30 - 介面 - Duck Typing 與 Polymorphism

  • 分享至 

  • xImage
  •  

一、本篇提要

  • Duck Typing
  • Polymorphism

二、值接收器、指標接收器與介面

前面實作 Speaker 及 Stringer 介面時所作,就是所謂 duck typing

duck typing 是程式設計中的一種推理 : 只要一個東西長得像鴨子、游泳像鴨子、叫聲也像鴨子,那麼它就是鴨子 !

Go 語言的duck typing,是根據型別方法來判斷型別符合介面,不是明確的指定哪些型別能夠符合

package main

import "fmt"


type Speaker interface {
    Speak() string
}

type cat struct {
}

func (c cat) Speak() string{
    return "Purrrrr Meow"
}

func chatter(s Speaker) { //接收speaker介面型別的引數
    fmt.Println(s.Speak())
}

func main() {
    c := cat{}
    chatter(c)
}

這次我們有一個函式 chatter(),它接收的參數型別是Speaker介面。既然cat結構隱性實作Speaker介面,因此他透過duck typing 被視為 Speaker 型別,可以傳入chatter()的參數。

duck typing 不只限於單一型別,接下來來看這個原理如何套用在多型。

三、Polymorphism

Polymorphism(多型),指一樣東西可以有多種形式呈現,簡單的例子 :

一個形狀可以有圓形、正方形、矩形或其他形狀。

在物件導向語言中,子類別(subclassing)的意思指的是讓一個類別去繼承父類別屬性和方法,並允許進一步定義或重寫它們。

Go 語言,雖然不是一個純粹的物件導向語言,它沒有類別(class),但它提供了內嵌結構和介面這兩個機制,這使得開發者能夠模仿物件導向語言中的某些功能。

在Go語言中使用多型的好處之一 :

如果只讓該函式接收介面型別參數,那麼任何符合介面規範的型別都可以傳入,甚至不需要在函式中撰寫額外程式碼去應付每一種型別。

進階例子-如何在Go中運用多型 :

前面提過,任何實質型別可以實作多個介面; 相反的,一個介面也可以被多個型別實作。

https://ithelp.ithome.com.tw/upload/images/20231002/20162693U3nloFk8ye.png

Speaker interface 中有 Speak() 方法,而 cat、dog、person 都實作 Speaker 介面,這代表他們一定都有Speak()方法,而且會回傳一個字串。

這表示我們可以寫一個共用函式接收Speaker介面型別的參數,然後對任何傳入的值呼叫相同行為 :

https://ithelp.ithome.com.tw/upload/images/20231002/20162693bPpBi7YkFz.png

package main

import "fmt"


type Speaker interface {
    Speak() string
}

type cat struct { //cat結構
}

type dog string // dog 自訂型別

type person struct { // person結構
    name string
}

func (c cat) Speak() string {
    return "Purrrrr Meow"
}

func (d dog) Speak() string {
    return "woof woof"
}

func (p person) Speak() string {
    return  p.name + ",誇爪kill,AAAAAAA"
}

func thingSpeak(s Speaker) {
    fmt.Println(s.Speak())
}

func main() {
    c := cat{}
    d := dog("")
    p := person{name: "Heather"}
    thingSpeak(c)
    thingSpeak(d)
    thingSpeak(p)
}

輸出結果 :

https://ithelp.ithome.com.tw/upload/images/20231002/2016269322JU0C5OpZ.png

可以看到函式 thingSpeak() 能夠接收 cat、dog、person 型別的值,並呼叫他們的Speak()值,
以下作出一點變化,將thingSpeak()方法改為可接收不定參數 :

func thingSpeak(speakers ...Speaker) {
    for _, s := range speakers {
        fmt.Println(s.Speak())
    }
}

thingSpeak()會去走訪 speakers (一個Speaker型別切片),並輪流呼叫每個元素Speak()方法。
接著修改main()中 :

thingSpeak(c, d, p)

完整程式 :

package main

import "fmt"


type Speaker interface {
    Speak() string
}

type cat struct { //cat結構
}

type dog string // dog 自訂型別

type person struct { // person結構
    name string
}

func (c cat) Speak() string {
    return "Purrrrr Meow"
}

func (d dog) Speak() string {
    return "woof woof"
}

func (p person) Speak() string {
    return p.name + ",誇爪kill,AAAAAAA"
}

func thingSpeak(speakers ...Speaker) {
    for _, s := range speakers {
        fmt.Println(s.Speak())
    }
}

func main() {
    c := cat{}
    d := dog("")
    p := person{name: "巨鎚瑞斯"}
    thingSpeak(c, d, p)
}

輸出結果也是一樣的,但是瞧瞧main()中,我們程式更加簡潔了!!!

以上就是duck typing與多型,接下來將作一個練習,熟悉一下。

四、 練習

寫一段程式

  1. 可以印出圓形、正方形、三角形的名稱與面積
  2. 負責印出資訊的函式會接收Shape這個介面型別的數量不定參數,使任何滿足Shape規範的形狀都可以傳入。
package main

import (
	"fmt"
	"math"
)


type Shape interface {
    Area() float64
    Name() string
}

type circle struct {
    radius float64
}

type square struct {
    side float64
}

type triangle struct {
    base float64
    height float64
}

func printSharpDetails(shapes ...Shape) {
    for _, item := range shapes {
        fmt.Printf("%s的面積: %.2f\n", item.Name(), item.Area())
    }
}

//實作面積
func (c circle) Area() float64 {
    return c.radius * c.radius * math.Pi
}

func (c circle) Name() string {
    return "圓形"
}

func (s square) Area() float64 {
    return s.side * s.side
}

func (s square) Name() string {
    return "正方形"
}

func (t triangle) Area() float64 {
    return (t.base * t.height ) / 2
}

func (t triangle) Name() string {
    return "三角形"
}


func main() {
    s := square{side:100}
    c := circle{radius: 6.5}
    t := triangle{base: 15.8, height: 22.3}
    printSharpDetails(s, c, t)
}

輸出結果 :

https://ithelp.ithome.com.tw/upload/images/20231002/201626935yp8M7DfZc.png

每一種形狀(circle、square、triangle)都滿足 Shape 介面,因為都具備 Area() 和 Name() 這兩種方法,且特徵也吻合。因此儘管每個結構的欄位有所不同,他們都可以被 printShapeDetails()函式使用。


上一篇
[Day17] Go in 30 - 介面(interface)
下一篇
[Day19] Go in 30 - 介面 - 在函式中活用介面
系列文
Go in 3o30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言