เหตุใดการกำหนดอินเทอร์เฟซจึงเข้มงวดกว่าการเรียกเมธอด

Aug 20 2020

ฉันกำลังผ่าน Tour of Go ฉันได้เรียนรู้ว่าหากเรามีวิธีการที่ยอมรับตัวชี้เป็นตัวรับก็จะยอมรับประเภทค่าเป็นตัวรับด้วย (ดำเนินการแปลงโดยอัตโนมัติ)

type Vertex struct { X, Y float64 }

func (v *Vertex) Abs() float64 {
    return math.Sqrt(v.X * v.X + v.Y * v.Y)
}

จากนั้นรหัสต่อไปนี้จะถูกต้องไม่ว่าจะได้รับ Vertex ด้วยค่าหรือตัวชี้

v := Vertex{1, 2}
fmt.Println(v.Abs())

p := &v
fmt.Println(p.Abs())

อย่างไรก็ตามสมมติว่าเรามีอินเทอร์เฟซต่อไปนี้:

type Abser interface {
    Abs() float64
}

แล้วเหตุใดรหัสต่อไปนี้จึงไม่ถูกต้อง?

var a Abser
v := Vertex{1, 2}

a = v // invalid

ความเข้าใจของฉันคือมันจะดี แม้ว่า v จะเป็นค่าประเภทที่ "ใช้" ฟังก์ชัน Abs ซึ่งรับตัวรับตัวชี้ แต่ก็จะรับค่าด้วย?

อินเทอร์เฟซถูกออกแบบมาให้เข้มงวดมากขึ้นในแง่ของสิ่งที่ตัวแปรอินเทอร์เฟซสามารถเก็บไว้ทางด้านขวามือหรือไม่? อินเทอร์เฟซจะเห็น * Vertex และ Vertex เป็นสองประเภทที่แตกต่างกันอย่างไรก็ตามวิธีการ Abs () ไม่มีปัญหาในการประมวลผลเช่นกัน

คำตอบ

3 Marc Aug 20 2020 at 12:32

ในทั้งสองกรณี Go ต้องการตัวชี้เพื่อเรียกใช้เมธอด ความแตกต่างที่สำคัญคือการเรียกเมธอดจะรับที่อยู่โดยอัตโนมัติvแต่ตรวจสอบว่ามีบางสิ่งที่ใช้อินเทอร์เฟซไม่ได้หรือไม่

วิธีการโทร:

เมื่อเรียกเมธอดด้วยตัวรับตัวชี้ในประเภทธรรมดา Go จะรับที่อยู่โดยอัตโนมัติหากได้รับอนุญาต จากข้อมูลจำเพาะเกี่ยวกับการเรียกวิธีการ (ไฮไลต์เป็นของฉัน):

การเรียกเมธอด xm () ใช้ได้ถ้าชุดวิธีของ (ประเภทของ) x มี m และรายการอาร์กิวเมนต์สามารถกำหนดให้กับรายการพารามิเตอร์ของ m ถ้า x สามารถระบุแอดเดรสได้และชุดวิธีของ & x มี m, xm () คือชวเลขสำหรับ (& x) .m ()

ในกรณีนี้xหมายถึงตัวแปรของคุณvและสามารถระบุแอดเดรสได้ (&v).Abs()ดังนั้นวิธีการในการโทรไปทำงานโดยอัตโนมัติ

การมอบหมาย:

เมื่อพยายามที่จะกำหนด ให้ตรวจสอบที่จะต้องเป็นa = v fullfilled ดำเนินการเฉพาะเมื่อชุดวิธีการตรงกับอินเทอร์เฟซ ชุดวิธีนี้ถูกกำหนดดังนี้:T is an interface type and x implements T.vAbser

ชุดวิธีการประเภทอื่น ๆ T ประกอบด้วยวิธีการทั้งหมดที่มีการประกาศรับ T ชุดวิธีการของชนิดตัวชี้ที่เกี่ยวข้อง * T คือชุดของวิธีการทั้งหมดที่ประกาศด้วยตัวรับ * T หรือ T (นั่นคือชุดวิธีการของ T)

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

สารละลาย:

วิธีแก้ปัญหานี้คือใช้ที่อยู่ของvคุณเอง:

var a Abser
v := Vertex{1, 2}

a = &v

โดยการทำเช่นนี้คุณกำลังมองหาที่วิธีการตั้งค่าของ*Vertexซึ่งไม่รวมถึงจึงใช้อินเตอร์เฟซAbs() float64Abser