ความหมายของอินเทอร์เฟซ {} คืออะไร?


140

ฉันเพิ่งเริ่มใช้อินเทอร์เฟซและพยายามทำ SOAP โดยgithub

ฉันไม่เข้าใจความหมายของ

Msg interface{}

ในรหัสนี้:

type Envelope struct {
    Body `xml:"soap:"`
}

type Body struct {
    Msg interface{}
}

ฉันสังเกตเห็นไวยากรณ์เดียวกันใน

fmt.Println

แต่ไม่เข้าใจว่าอะไรคือความสำเร็จ

interface{}

21
interface{}มากหรือน้อยเทียบเท่ากับvoid *ใน C มันสามารถชี้ไปที่อะไรก็ได้และคุณต้องมีการยืนยันการร่าย / ประเภทเพื่อใช้งาน
Nick Craig-Wood

ความหมายของอินเทอร์เฟซ {} คืออะไร? ดูstackoverflow.com/a/62337836/12817546
Tom L

คำตอบ:


195

คุณสามารถดูบทความ " วิธีใช้อินเทอร์เฟซใน Go " (อ้างอิงจาก " คำอธิบายอินเทอร์เฟซของ Russ Cox "):

สิ่งที่เป็นอินเตอร์เฟซ?

อินเทอร์เฟซเป็นสองสิ่ง:

  • มันคือชุดของวิธีการ
  • แต่ก็เป็นประเภทด้วย

interface{}ประเภทที่อินเตอร์เฟซที่ว่างเปล่าเป็นอินเตอร์เฟซที่มีวิธีการใด ๆ

เนื่องจากมีการดำเนินการไม่มีคำหลักทุกประเภทดำเนินการอย่างน้อยศูนย์วิธีการและความพึงพอใจของอินเตอร์เฟซจะทำโดยอัตโนมัติทุกประเภทตอบสนองอินเตอร์เฟซที่ว่างเปล่า
นั่นหมายความว่าถ้าคุณเขียนฟังก์ชั่นที่ใช้เวลาอีกด้วยinterface{}ค่าเป็นพารามิเตอร์ที่คุณสามารถใส่ฟังก์ชั่นที่มีค่าใด

(นั่นคือสิ่งที่Msgแสดงถึงในคำถามของคุณ: ค่าใด ๆ )

func DoSomething(v interface{}) {
   // ...
}

นี่คือสิ่งที่ทำให้เกิดความสับสน:

ข้างในDoSomethingฟังก์ชั่นคือvอะไร?

นักโกเฟอร์มือใหม่มักเชื่อว่า“ vเป็นประเภทใดก็ได้” แต่นั่นไม่ถูกต้อง
vไม่ใช่ประเภทใด ๆ มันเป็นของinterface{}ประเภท

เมื่อผ่านเข้าไปในค่าDoSomethingฟังก์ชั่นรันไทม์ไปจะดำเนินการแปลงชนิด (ถ้าจำเป็น) และแปลงค่าไปยังinterface{}ค่า
ค่าทั้งหมดมีอีกหนึ่งประเภทที่รันไทม์และv's interface{}ประเภทคงที่หนึ่งคือ

ค่าอินเทอร์เฟซสร้างจากข้อมูลสองคำ :

  • คำหนึ่งคำใช้ชี้ไปที่ตารางวิธีการสำหรับประเภทพื้นฐานของค่า
  • และอีกคำหนึ่งใช้เพื่อชี้ไปที่ข้อมูลจริงที่ถูกเก็บไว้ด้วยค่านั้น

ภาคผนวก: นี่เป็นบทความของ Russ ที่ค่อนข้างสมบูรณ์เกี่ยวกับโครงสร้างอินเทอร์เฟซ:

type Stringer interface {
    String() string
}

ค่าอินเทอร์เฟซจะแสดงเป็นคู่คำสองคำโดยให้ตัวชี้ไปยังข้อมูลเกี่ยวกับประเภทที่จัดเก็บในอินเทอร์เฟซและตัวชี้ไปยังข้อมูลที่เกี่ยวข้อง
การกำหนด b เป็นค่าอินเทอร์เฟซของประเภท Stringer จะตั้งค่าทั้งสองคำของค่าอินเตอร์เฟส

http://research.swtch.com/gointer2.png

คำแรกในค่าอินเทอร์เฟซชี้ไปที่สิ่งที่ฉันเรียกว่าตารางอินเทอร์เฟซหรือ itable (ออกเสียงว่า i-table ในแหล่งรันไทม์ชื่อการใช้งาน C คือ Itab)
itable เริ่มต้นด้วยข้อมูลเมตาบางอย่างเกี่ยวกับประเภทที่เกี่ยวข้องจากนั้นจะกลายเป็นรายการตัวชี้ฟังก์ชัน
หมายเหตุว่าสอดคล้อง itable ประเภทอินเตอร์เฟซที่ไม่ได้ชนิดที่แบบไดนามิก
ในแง่ของตัวอย่างของเรา itable สำหรับการStringerถือประเภท Binary จะแสดงรายการวิธีการที่ใช้เพื่อตอบสนอง Stringer ซึ่งเป็นเพียงString: วิธีการอื่น ๆ ของ Binary ( Get) ไม่ปรากฏในไฟล์itable.

คำที่สองในค่าอินเทอร์เฟซชี้ไปที่ข้อมูลจริงในกรณีนี้คือสำเนาของb.
งานvar s Stringer = bจะทำสำเนาbแทนที่จะชี้ที่bด้วยเหตุผลเดียวกับที่var c uint64 = bทำสำเนา: หากมีbการเปลี่ยนแปลงในภายหลังsและcควรมีค่าเดิมไม่ใช่ค่าใหม่
ค่าที่จัดเก็บในอินเทอร์เฟซอาจมีขนาดใหญ่ตามอำเภอใจ แต่มีเพียงคำเดียวเท่านั้นที่ใช้ในการเก็บค่าในโครงสร้างอินเทอร์เฟซดังนั้นการกำหนดจะจัดสรรหน่วยความจำส่วนหนึ่งบนฮีปและบันทึกตัวชี้ในช่องคำเดียว


4
"ข้อมูลสองคำ" หมายความว่าอย่างไร โดยเฉพาะ "คำ" หมายถึงอะไร?
Mingyu

3
@ หมิงหยูฉันตอบเสร็จแล้วเพื่ออธิบายสองคำนั้น (คะแนน 32 บิต)
VonC

2
@Mingyu: VonC หมายถึงคำในความหมายของสถาปัตยกรรมคอมพิวเตอร์ - ชุดของบิตที่กำหนดชิ้นส่วนข้อมูลขนาดคงที่ ขนาดคำถูกควบคุมโดยสถาปัตยกรรมโปรเซสเซอร์ที่คุณใช้
Dan Esparza

1
ขอบคุณ @VonC สำหรับการตอบกลับของคุณ ... ความจริงก็คือฉันเบื่อกับการลงข้อความเมื่อฉันถามสิ่งต่างๆ .. คนส่วนใหญ่มักบอกฉันว่าฉันควรอ่านเอกสาร ... ฉันจะจำคำแนะนำของคุณได้ถ้าฉันรู้สึกด้วย จะเขียนโพสต์ให้ถูกต้อง ... แต่ฉันคิดไม่ออกจริงๆว่าจะถามทางอื่น ยังไงก็ขอบคุณและขอโทษที่ต่ำช้าของฉัน คุณสามารถดูสิ่งนี้ได้ที่: stackoverflow.com/questions/45577301/…เพื่อชี้แจงว่าทำไมฉันถึงไม่ชอบถาม
วิกเตอร์

1
@vic ไม่มีปัญหาและขออภัยสำหรับประสบการณ์ที่ไม่ดีก่อนหน้านี้ในฐานะผู้ถาม เป็นเพียงความคิดเห็นที่ไม่เหมาะสมสำหรับคำถามและคำตอบ
VonC

34

interface{}หมายความว่าคุณสามารถใส่ค่าประเภทใดก็ได้รวมทั้งประเภทที่คุณกำหนดเอง ทุกประเภทใน Go ตอบสนองอินเทอร์เฟซที่ว่างเปล่า ( interface{}เป็นอินเทอร์เฟซที่ว่างเปล่า)
ในตัวอย่างของคุณฟิลด์ Msg สามารถมีค่าประเภทใดก็ได้

ตัวอย่าง:

package main

import (
    "fmt"
)

type Body struct {
    Msg interface{}
}

func main() {
    b := Body{}
    b.Msg = "5"
    fmt.Printf("%#v %T \n", b.Msg, b.Msg) // Output: "5" string
    b.Msg = 5

    fmt.Printf("%#v %T", b.Msg, b.Msg) //Output:  5 int
}

ไปที่ Playground


12

เรียกว่าอินเทอร์เฟซว่างและใช้งานได้ทุกประเภทซึ่งหมายความว่าคุณสามารถใส่อะไรก็ได้ในMsgฟิลด์

ตัวอย่าง:

body := Body{3}
fmt.Printf("%#v\n", body) // -> main.Body{Msg:3}

body = Body{"anything"}
fmt.Printf("%#v\n", body) // -> main.Body{Msg:"anything"}

body = Body{body}
fmt.Printf("%#v\n", body) // -> main.Body{Msg:main.Body{Msg:"anything"}}

นี่คือส่วนขยายเชิงตรรกะของข้อเท็จจริงที่ว่าประเภทใช้อินเทอร์เฟซทันทีที่มีวิธีการทั้งหมดของอินเทอร์เฟซ


หมายความว่าอาจเป็นโครงสร้างที่กำหนดโดยผู้ใช้
ผู้ใช้

11

มีคำตอบที่ดีอยู่แล้วที่นี่ ให้ฉันเพิ่มของฉันเองด้วยสำหรับคนอื่น ๆ ที่ต้องการเข้าใจโดยสัญชาตญาณ:


อินเตอร์เฟซ

นี่คืออินเทอร์เฟซด้วยวิธีการเดียว:

type Runner interface {
    Run()
}

ดังนั้นทุกประเภทที่มีRun()วิธีการตรงตามอินเทอร์เฟซ Runner:

type Program struct {
    /* fields */
}

func (p Program) Run() {
    /* running */
}

func (p Program) Stop() {
    /* stopping */
}
  • แม้ว่าประเภทโปรแกรมจะมีเมธอด Stop แต่ก็ยังคงเป็นไปตามอินเทอร์เฟซ Runner เนื่องจากสิ่งที่จำเป็นคือต้องมีวิธีการทั้งหมดของอินเทอร์เฟซเพื่อตอบสนอง

  • ดังนั้นจึงมีวิธีการเรียกใช้และตรงตามอินเทอร์เฟซ Runner


อินเทอร์เฟซว่างเปล่า

นี่คืออินเทอร์เฟซว่างเปล่าที่มีชื่อโดยไม่มีวิธีการใด ๆ :

type Empty interface {
    /* it has no methods */
}

ดังนั้นทุกประเภทก็ตรงตามอินเทอร์เฟซนี้ เนื่องจากไม่จำเป็นต้องใช้วิธีใดเพื่อตอบสนองอินเทอร์เฟซนี้ ตัวอย่างเช่น:

// Because, Empty interface has no methods, following types satisfy the Empty interface
var a Empty

a = 5
a = 6.5
a = "hello"

แต่โปรแกรมประเภทด้านบนตอบสนองหรือไม่ ใช่:

a = Program{} // ok

อินเทอร์เฟซ {} เท่ากับอินเทอร์เฟซว่างเปล่าด้านบน

var b interface{}

// true: a == b

b = a
b = 9
b = "bye"

อย่างที่คุณเห็นไม่มีอะไรลึกลับเกี่ยวกับเรื่องนี้ แต่มันง่ายมากที่จะละเมิด อยู่ห่างจากมันให้มากที่สุด


https://play.golang.org/p/A-vwTddWJ7G


type Runner interfaceไม่ได้ใช้ในตัวอย่างสนามเด็กเล่นไป
Tom L

9

จากข้อมูลจำเพาะของ Golang :

ประเภทอินเทอร์เฟซระบุชุดวิธีการที่เรียกว่าอินเตอร์เฟส ตัวแปรประเภทอินเทอร์เฟซสามารถจัดเก็บค่าประเภทใดก็ได้ด้วยชุดวิธีการที่เป็นส่วนเหนือของอินเทอร์เฟซ ประเภทดังกล่าวมีการกล่าวถึงการใช้งานอินเทอร์เฟซ ค่าของตัวแปรที่ไม่ได้กำหนดค่าเริ่มต้นของประเภทอินเทอร์เฟซคือศูนย์

ประเภทใช้อินเทอร์เฟซใด ๆ ที่ประกอบด้วยส่วนย่อยของวิธีการใด ๆ ดังนั้นจึงอาจใช้อินเทอร์เฟซที่แตกต่างกันหลายอย่าง ตัวอย่างเช่นทุกประเภทใช้อินเทอร์เฟซว่าง:

อินเตอร์เฟซ{}

แนวคิดในการสร้างกราฟคือ:

  1. ทุกอย่างมีประเภท คุณสามารถกำหนดรูปแบบใหม่ขอเรียกมันตันสมมติว่าตอนนี้ประเภทของเราTมี 3 วิธี: A, ,BC
  2. ชุดวิธีการที่ระบุสำหรับประเภทเรียกว่า " ประเภทอินเทอร์เฟซ " เรียกในตัวอย่างของเราว่า T_interface เท่ากับT_interface = (A, B, C)
  3. คุณสามารถสร้าง "ประเภทอินเทอร์เฟซ" โดยกำหนดลายเซ็นของวิธีการMyInterface = (A, )
  4. เมื่อคุณระบุตัวแปรของประเภท "ประเภทอินเตอร์เฟซ" คุณสามารถกำหนดให้เพียงประเภทที่มีอินเตอร์เฟซที่เป็น superset ของอินเตอร์เฟซของคุณ นั่นหมายความว่าMyInterfaceจะต้องมีวิธีการทั้งหมดที่มีอยู่ภายในT_interface

คุณสามารถอนุมานได้ว่า "ประเภทอินเทอร์เฟซ" ทั้งหมดทุกประเภทเป็นส่วนเหนือของอินเทอร์เฟซที่ว่างเปล่า


1

ตัวอย่างที่ขยายคำตอบที่ยอดเยี่ยมโดย @VonC และความคิดเห็นโดย @ NickCraig-Wood interface{}สามารถชี้ไปที่อะไรก็ได้และคุณต้องมีการยืนยันตัวละคร / ประเภทเพื่อใช้งาน

package main

import (
    . "fmt"
    "strconv"
)

var c = cat("Fish")
var d = dog("Bone")

func main() {
    var i interface{} = c
    switch i.(type) {
    case cat:
        c.Eat() // Fish
    }

    i = d
    switch i.(type) {
    case dog:
        d.Eat() // Bone
    }

    i = "4.3"
    Printf("%T %v\n", i, i) // string 4.3
    s, _ := i.(string)      // type assertion
    f, _ := strconv.ParseFloat(s, 64)
    n := int(f)             // type conversion
    Printf("%T %v\n", n, n) // int 4
}

type cat string
type dog string
func (c cat) Eat() { Println(c) }
func (d dog) Eat() { Println(d) }

icat("Fish")เป็นตัวแปรของอินเตอร์เฟซที่ว่างเปล่าที่มีค่า การสร้างค่าวิธีการจากค่าประเภทอินเทอร์เฟซถูกกฎหมาย ดูhttps://golang.org/ref/spec#Interface_types

ประเภทสวิทช์ยืนยันชนิดของอินเตอร์เฟซi cat("Fish")ดูhttps://golang.org/doc/effective_go.html#type_switch เป็นพระราชเสาวนีย์แล้วi dog("Bone")ประเภทสวิทช์ยืนยันว่าชนิดของอินเตอร์เฟซที่มีการเปลี่ยนแปลงไปidog("Bone")

นอกจากนี้คุณยังสามารถขอให้รวบรวมเพื่อตรวจสอบว่าชนิดTการดำเนินการอินเตอร์เฟซโดยพยายามที่ได้รับมอบหมาย:I var _ I = T{}ดูhttps://golang.org/doc/faq#guarantee_s Satisfies_interfaceและhttps://stackoverflow.com/a/60663003/12817546 https://stackoverflow.com/a/60663003/12817546

interface{}ทุกประเภทใช้อินเตอร์เฟซที่ว่างเปล่า ดูhttps://talks.golang.org/2012/goforc.slide#44และhttps://golang.org/ref/spec#Interface_types ในตัวอย่างนี้iถูกกำหนดใหม่คราวนี้เป็นสตริง "4.3" iที่ได้รับมอบหมายไปแล้วตัวแปรสตริงใหม่sกับi.(string)ก่อนที่sจะถูกแปลงเป็นประเภท float64 ใช้f strconvสุดท้ายfจะถูกแปลงnเป็นประเภท int เท่ากับ 4 ดูอะไรคือความแตกต่างระหว่างการแปลงประเภทและการยืนยันประเภท?

แผนที่และชิ้นส่วนในตัวของ Go รวมทั้งความสามารถในการใช้อินเทอร์เฟซที่ว่างเปล่าเพื่อสร้างคอนเทนเนอร์ (ด้วยการแกะกล่องอย่างชัดเจน) หมายความว่าในหลาย ๆ กรณีเป็นไปได้ที่จะเขียนโค้ดที่ทำสิ่งที่สามารถเปิดใช้งานทั่วไปได้หากราบรื่นน้อยกว่า ดูhttps://golang.org/doc/faq#generics


ถอดรหัสรหัสด้วยอินเทอร์เฟซ ดูstackoverflow.com/a/62297796/12817546 เรียกวิธีการ "แบบไดนามิก" ดูstackoverflow.com/a/62336440/12817546 เข้าถึงแพ็คเกจ Go ดูstackoverflow.com/a/62278078/12817546 กำหนดค่าใด ๆ ให้กับตัวแปร ดูstackoverflow.com/a/62337836/12817546
Tom L
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.