“ <type> เป็นตัวชี้ไปที่อินเทอร์เฟซไม่ใช่อินเทอร์เฟซ” เกิดความสับสน


117

เรียนเพื่อนนักพัฒนา

ฉันมีปัญหานี้ซึ่งดูเหมือนจะแปลกสำหรับฉัน ดูตัวอย่างโค้ดนี้:

package coreinterfaces

type FilterInterface interface {
    Filter(s *string) bool
}

type FieldFilter struct {
    Key string
    Val string
}

func (ff *FieldFilter) Filter(s *string) bool {
    // Some code
}

type FilterMapInterface interface {
    AddFilter(f *FilterInterface) uuid.UUID     
    RemoveFilter(i uuid.UUID)                   
    GetFilterByID(i uuid.UUID) *FilterInterface
}

type FilterMap struct {
    mutex   sync.Mutex
    Filters map[uuid.UUID]FilterInterface
}

func (fp *FilterMap) AddFilter(f *FilterInterface) uuid.UUID {
    // Some code
}

func (fp *FilterMap) RemoveFilter(i uuid.UUID) {
    // Some code
}

func (fp *FilterMap) GetFilterByID(i uuid.UUID) *FilterInterface {
    // Some code
}

ในแพ็คเกจอื่นฉันมีรหัสต่อไปนี้:

func DoFilter() {
    fieldfilter := &coreinterfaces.FieldFilter{Key: "app", Val: "152511"}
    filtermap := &coreinterfaces.FilterMap{}
    _ = filtermap.AddFilter(fieldfilter) // <--- Exception is raised here
}

เวลาทำงานจะไม่ยอมรับบรรทัดที่กล่าวถึงเนื่องจาก

"ไม่สามารถใช้ fieldfilter (พิมพ์ * coreinterfaces.FieldFilter) เป็น type * coreinterfaces.FilterInterface ในการโต้แย้ง fieldint.AddFilter: * coreinterfaces.FilterInterface คือตัวชี้ไปยังอินเทอร์เฟซไม่ใช่อินเทอร์เฟซ"

อย่างไรก็ตามเมื่อเปลี่ยนรหัสเป็น:

func DoBid() error {
    bs := string(b)
    var ifilterfield coreinterfaces.FilterInterface
    fieldfilter := &coreinterfaces.FieldFilter{Key: "app", Val: "152511"}
    ifilterfield = fieldfilter
    filtermap := &coreinterfaces.FilterMap{}
    _ = filtermap.AddFilter(&ifilterfield)
}

ทุกอย่างเรียบร้อยดีและเมื่อทำการดีบักแอปพลิเคชันดูเหมือนว่าจะรวมอยู่ด้วย

ฉันสับสนในหัวข้อนี้เล็กน้อย เมื่อดูบล็อกโพสต์อื่น ๆ และเธรดล้นสแต็กที่พูดถึงปัญหาเดียวกันนี้ (เช่น - นี่หรือ นี่ ) ตัวอย่างแรกที่ทำให้เกิดข้อยกเว้นนี้ควรใช้งานได้เนื่องจากทั้งฟิลเตอร์ฟิลด์และแมปฟิลด์ถูกเตรียมใช้งานเป็นตัวชี้ไปยังอินเทอร์เฟซแทนที่จะเป็นค่าของ อินเทอร์เฟซ ฉันไม่สามารถคาดเดาสิ่งที่เกิดขึ้นจริงที่นี่ได้ซึ่งฉันจำเป็นต้องเปลี่ยนแปลงเพื่อไม่ให้ฉันประกาศ FieldInterface และกำหนดการใช้งานสำหรับอินเทอร์เฟซนั้น ต้องมีวิธีที่สง่างามในการทำเช่นนี้


เมื่อเปลี่ยน* FilterInterfaceเป็นFilterInterfaceThe line _ = filtermap.AddFilter(fieldfilter)จะเพิ่มสิ่งนี้: ไม่สามารถใช้ fieldfilter (พิมพ์ coreinterfaces.FieldFilter) เป็นชนิด coreinterfaces.FilterInterface ในการโต้แย้ง filtermap.AddFilter: coreinterfaces.FieldFilter ไม่ใช้ coreinterfaces.FilterInterface (วิธีการกรองมีตัวรับตัวชี้)อย่างไรก็ตามเมื่อเปลี่ยน บรรทัด_ = filtermap.AddFilter(&fieldfilter)มันทำงาน เกิดอะไรขึ้นที่นี่? ทำไมเป็นอย่างนั้น?
0rka

2
เนื่องจากวิธีการที่ใช้อินเทอร์เฟซมีตัวรับตัวชี้ การส่งผ่านค่าจะไม่ใช้อินเทอร์เฟซ การส่งผ่านตัวชี้จะเป็นเพราะวิธีการนั้นใช้ โดยทั่วไปเมื่อจัดการกับอินเทอร์เฟซคุณจะส่งตัวชี้ไปยังโครงสร้างไปยังฟังก์ชันที่ต้องการอินเทอร์เฟซ คุณแทบไม่ต้องการตัวชี้ไปยังอินเทอร์เฟซในทุกสถานการณ์
Adrian

1
ฉันเข้าใจประเด็นของคุณ แต่การเปลี่ยนค่าพารามิเตอร์จาก* FilterInterfaceเป็นโครงสร้างที่ใช้อินเทอร์เฟซนี้จะทำให้แนวคิดในการส่งผ่านอินเตอร์เฟสไปยังฟังก์ชันต่างๆ สิ่งที่ฉันต้องการทำให้สำเร็จไม่ใช่การผูกมัดกับโครงสร้างที่ฉันกำลังส่งผ่าน แต่เป็นโครงสร้างใด ๆที่ใช้อินเทอร์เฟซที่ฉันสนใจที่จะใช้ การเปลี่ยนแปลงโค้ดใด ๆ ที่คุณคิดว่ามีประสิทธิภาพมากกว่าหรือเป็นไปตามมาตรฐานสำหรับฉันที่จะทำ? ฉันยินดีที่จะใช้บริการตรวจสอบโค้ด :)
0rka

2
ฟังก์ชันของคุณควรยอมรับอาร์กิวเมนต์อินเทอร์เฟซ (ไม่ใช่ตัวชี้ไปที่อินเทอร์เฟซ) ผู้โทรควรส่งผ่านตัวชี้ไปยังโครงสร้างที่ใช้อินเทอร์เฟซ สิ่งนี้ไม่ได้ "ทำลายความคิดในการส่งต่ออินเทอร์เฟซไปยังฟังก์ชัน" - ฟังก์ชันนี้ยังคงใช้อินเทอร์เฟซอยู่คุณกำลังส่งต่อในขั้นตอนการสรุปที่ใช้อินเทอร์เฟซ
Adrian

คำตอบ:


157

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

type Fooer interface {
    Dummy()
}

type Foo struct{}

func (f Foo) Dummy() {}

func main() {
    var f1 Foo
    var f2 *Foo = &Foo{}

    DoFoo(f1)
    DoFoo(f2)
}

func DoFoo(f Fooer) {
    fmt.Printf("[%T] %+v\n", f, f)
}

เอาท์พุต:

[main.Foo] {}
[*main.Foo] &{}

https://play.golang.org/p/I7H_pv5H3Xl

ในทั้งสองกรณีfตัวแปรในDoFooเป็นเพียงอินเทอร์เฟซไม่ใช่ตัวชี้ไปยังอินเทอร์เฟซ อย่างไรก็ตามเมื่อจัดเก็บf2อินเทอร์เฟซจะมีตัวชี้ไปที่Fooโครงสร้าง

ตัวชี้ไปยังอินเทอร์เฟซแทบจะไม่มีประโยชน์เลย ในความเป็นจริงรันไทม์ของ Go ได้รับการเปลี่ยนแปลงโดยเฉพาะบางเวอร์ชันกลับไปสู่การไม่ยกเลิกการอ้างอิงตัวชี้อินเทอร์เฟซโดยอัตโนมัติอีกต่อไป (เช่นเดียวกับตัวชี้โครงสร้าง) เพื่อกีดกันการใช้งาน ในกรณีส่วนใหญ่ที่ครอบงำตัวชี้ไปยังอินเทอร์เฟซสะท้อนให้เห็นถึงความเข้าใจผิดว่าอินเทอร์เฟซควรจะทำงานอย่างไร

อย่างไรก็ตามมีข้อ จำกัด เกี่ยวกับอินเทอร์เฟซ หากคุณส่งผ่านโครงสร้างไปยังอินเทอร์เฟซโดยตรงจะสามารถใช้เฉพาะเมธอดค่าประเภทนั้น ๆ เท่านั้น (เช่นfunc (f Foo) Dummy()ไม่func (f *Foo) Dummy()) เพื่อเติมเต็มอินเทอร์เฟซ เนื่องจากคุณกำลังจัดเก็บสำเนาของโครงสร้างดั้งเดิมในอินเทอร์เฟซดังนั้นวิธีการของตัวชี้จะมีผลกระทบที่ไม่คาดคิด (เช่นไม่สามารถปรับเปลี่ยนโครงสร้างเดิมได้) ดังนั้นกฎเริ่มต้นคือการจัดเก็บพอยน์เตอร์ไปยังโครงสร้างในอินเทอร์เฟซเว้นแต่จะมีเหตุผลที่น่าสนใจที่จะไม่ทำ

โดยเฉพาะกับรหัสของคุณหากคุณเปลี่ยนลายเซ็นฟังก์ชัน AddFilter เป็น:

func (fp *FilterMap) AddFilter(f FilterInterface) uuid.UUID

และลายเซ็น GetFilterByID เพื่อ:

func (fp *FilterMap) GetFilterByID(i uuid.UUID) FilterInterface

รหัสของคุณจะทำงานตามที่คาดไว้ fieldfilterเป็นประเภท*FieldFilterที่เติมเต็มFilterInterfaceประเภทอินเทอร์เฟซจึงAddFilterจะยอมรับได้

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


"นี่เป็นเพราะคุณกำลังจัดเก็บสำเนาของโครงสร้างดั้งเดิมในอินเทอร์เฟซดังนั้นวิธีการชี้จะมีผลกระทบที่ไม่คาดคิด (เช่นไม่สามารถปรับเปลี่ยนโครงสร้างเดิมได้)" ซึ่งไม่สมเหตุสมผลสำหรับข้อ จำกัด ท้ายที่สุดสำเนาเพียงชุดเดียวอาจถูกเก็บไว้ในอินเทอร์เฟซมาโดยตลอด
WPWoodJr

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

5
GetFilterByID(i uuid.UUID) *FilterInterface

เมื่อฉันได้รับข้อผิดพลาดนี้มักเป็นเพราะฉันกำลังระบุตัวชี้ไปที่อินเทอร์เฟซแทนที่จะเป็นอินเทอร์เฟซ (ซึ่งจะเป็นตัวชี้ไปยังโครงสร้างของฉันที่เติมเต็มอินเทอร์เฟซ)

มีการใช้งานที่ถูกต้องสำหรับ * อินเทอร์เฟซ {... } แต่โดยทั่วไปแล้วฉันคิดว่า 'นี่คือตัวชี้' แทนที่จะเป็น 'นี่คืออินเทอร์เฟซที่เป็นตัวชี้ในโค้ดที่ฉันกำลังเขียน'

เพียงแค่โยนมันออกไปเพราะคำตอบที่ยอมรับแม้ว่าจะมีรายละเอียด แต่ก็ไม่ได้ช่วยฉันแก้ปัญหา

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.