1 · interface คือรายชื่อ method เฉย ๆ
type Notifier interface {
Notify(message string) error
}
อ่านว่า: "อะไรก็ตามที่มี method ชื่อ Notify รับ string
คืน error — ถือว่าเป็น Notifier"
ไม่มีการบอกว่าข้างในทำงานยังไง มีแค่รูปร่างของ method
เต้ารับบนกำแพงไม่รู้จักพัดลม ไม่รู้จักที่ชาร์จมือถือ มันสนใจอย่างเดียวคือ “หัวปลั๊กที่ยื่นมามีรูปร่างตรงกับรูไหม” และไม่มีใครต้องไปแจ้งการไฟฟ้าก่อนว่าพัดลมตัวนี้ขอขึ้นทะเบียนเป็นของที่เสียบเต้ารับได้ — รูปร่างตรง ก็เสียบได้เลย
Go คิดแบบเดียวกัน: interface = รูปร่างของรูเต้ารับ · method = ขาปลั๊กแต่ละขา · ครบขาเมื่อไหร่ก็เสียบได้ทันที
ที่ไม่เหมือนกัน: เต้ารับรู้ตอนเราเสียบจริง แต่ Go ตรวจให้ตั้งแต่ตอน compile · และที่ต้องระวังคือทั้งคู่ตรวจแค่ “รูปร่าง” — ปลั๊กเสียบเข้าไม่ได้แปลว่าพัดลมจะหมุน เหมือนที่ method ครบตาม interface ไม่ได้แปลว่าข้างในเขียนถูก
ต่างจากภาษาอื่นตรงไหน
ภาษาอื่น (explicit)
class EmailNotifier
implements Notifier {
// ...
}
Go (implicit)
type EmailNotifier struct{}
func (EmailNotifier) Notify(
message string,
) error {
return nil
}
ฝั่ง Go ไม่มีคำว่า Notifier โผล่มาเลยสักที่
แต่ EmailNotifier ก็เป็น Notifier เรียบร้อยแล้ว
เรื่องนี้เรียกว่า structural typing หรือชื่อเล่นที่ติดปากกว่าคือ…
2 · Duck Typing
"If it walks like a duck and it quacks like a duck, then it must be a duck"
type DuckLike interface {
Walk()
Quack()
}
type RealDuck struct{}
func (RealDuck) Walk() { fmt.Println("Duck is walking") }
func (RealDuck) Quack() { fmt.Println("Quack!") }
type ToyDuck struct{}
func (ToyDuck) Walk() { fmt.Println("Toy duck moves forward") }
func (ToyDuck) Quack() { fmt.Println("Electronic quack!") }
func ObserveDuck(d DuckLike) {
d.Walk()
d.Quack()
}
ObserveDuck(RealDuck{}) // Duck is walking / Quack!
ObserveDuck(ToyDuck{}) // Toy duck moves forward / Electronic quack!
ObserveDuck ไม่รู้จัก ToyDuck เลย และ ToyDuck
ก็ไม่เคยประกาศว่าเป็น DuckLike — compiler
(ตัวแปลโค้ดของเราให้เป็นโปรแกรมที่เครื่องรันได้) จับคู่ให้เองตอนที่คุณส่งเข้าไป
ใช้คำให้ตรง แล้วคำตอบที่ได้จาก senior หรือจาก AI จะตรงขึ้นทันที
- implement (อิมพลีเมนต์)
- การที่ type หนึ่งมี method ครบตามที่ interface ขอ · พูดว่า “EmailNotifier implement Notifier แล้ว” แทน “มันใช้กับ Notifier ได้”
- method signature
- “รูปร่างของ method” = ชื่อ + ชนิดที่รับเข้า + ชนิดที่คืนออก ต้องตรงครบทั้งสาม · เวลาโค้ดไม่ผ่านให้พูดว่า “signature ไม่ตรง” แทน “มันไม่ยอมให้ใส่”
- structural typing
- ตัดสินจากรูปร่างว่ามี method อะไรบ้าง ไม่ใช่จากชื่อที่ประกาศไว้ · ของ Go เป็นแบบ static คือตรวจให้ครบตั้งแต่ตอน compile · ประโยคที่ใช้ได้เลย: “Go ใช้ interface แบบ implicit (ไม่ต้องเขียนบอก) เพราะเป็น structural typing”
- duck typing
- คำที่ติดมาจากภาษาอย่าง Python/Ruby ซึ่งเรียก method ไปก่อน ถ้าไม่มีค่อยพังตอนรัน · คนเรียก Go ว่า duck typing กันบ่อยเพราะไอเดีย “ดูรูปร่าง” เหมือนกัน แต่จังหวะตรวจคนละจังหวะ · ถ้าจะพูดให้ตรงเป๊ะกับ senior ให้ใช้คำว่า structural typing แบบ static
3 · ทำไมต้องมี — ปัญหาของโค้ดที่ผูกติดตายตัว
// สมมติ Checkout Service ผูกกับ Paypal โดยตรง
type CheckoutService struct {
paypal PaypalGateway
}
func (s CheckoutService) Checkout(amount int64) error {
return s.paypal.Charge(amount)
}
โค้ดนี้ทำงานได้ แต่พังทันทีเมื่อ:
- บริษัทเปลี่ยนไปใช้ GooglePay → ต้องแก้ทั้ง business logic (โค้ดส่วนที่เป็นขั้นตอนทางธุรกิจจริง ๆ เช่น ขั้นตอนตอนลูกค้ากดสั่งซื้อ)
- อยากเขียน test → ต้องยิงเงินจริงเข้า PayPal
- อยากรองรับสองเจ้าพร้อมกัน → ต้อง copy โค้ดทั้งก้อน
แคชเชียร์ทำงานเดิมทุกวัน: สแกนของ → บอกยอด → รับชำระ
ถ้าคู่มือเขียนว่า “รูดบัตรกับเครื่องของ PayPal ที่วางอยู่ซ้ายมือ”
วันที่ร้านเปลี่ยนเจ้า ต้องพิมพ์คู่มือใหม่ทั้งเล่ม
แต่ถ้าเขียนว่า “รับชำระด้วยเครื่องที่วางอยู่ตรงช่องนี้”
ก็แค่ยกเครื่องเก่าออก วางเครื่องใหม่ลงช่องเดิม — คู่มือไม่ต้องแก้สักบรรทัด
และตอนเทรนพนักงานใหม่ ก็วางเครื่องปลอมที่ไม่ตัดเงินจริงลงช่องเดิมได้ (= ตอนเขียน test)
PaymentGateway คือช่องวางเครื่อง · PaypalGateway คือเครื่องยี่ห้อหนึ่ง ·
CheckoutService คือคู่มือที่ไม่ต้องรู้ยี่ห้อเลย
ที่ไม่เหมือนกัน: แคชเชียร์เงยหน้ามาก็เห็นว่าเครื่องยี่ห้ออะไร
แต่ CheckoutService มองไม่เห็นอะไรนอกจาก method ที่ interface ระบุไว้
ต่อให้ PaypalGateway มี method อื่นอีกสิบตัวก็เรียกไม่ได้
(จะแอบดูของข้างในต้องใช้ type assertion ใน
บทเรียน 11)
เขียนใหม่ด้วย interface
// เริ่มจากกำหนด Contract กลาง:
type PaymentGateway interface {
Pay(amount int64) error
}
// Checkout Service สนใจแค่ว่า "จ่ายเงินได้" ไม่สนว่าเจ้าไหน
type CheckoutService struct {
gateway PaymentGateway
}
func NewCheckoutService(gateway PaymentGateway) *CheckoutService {
return &CheckoutService{gateway: gateway}
}
func (s *CheckoutService) Checkout(amount int64) error {
return s.gateway.Pay(amount)
}
// Implementation แต่ละเจ้า อยู่คนละไฟล์ก็ได้:
type PaypalGateway struct{}
func (PaypalGateway) Pay(amount int64) error {
fmt.Printf("Paid %d satang via Paypal\n", amount)
return nil
}
type GooglePayGateway struct{}
func (GooglePayGateway) Pay(amount int64) error {
fmt.Printf("Paid %d satang via GPay\n", amount)
return nil
}
// สลับเจ้าได้ที่จุดเดียว — business logic ไม่ต้องแก้เลย
service := NewCheckoutService(PaypalGateway{})
service = NewCheckoutService(GooglePayGateway{})
ตอนเขียน test คุณสร้าง type FakeGateway struct{} ที่มี
Pay() คืน nil เฉย ๆ แล้วยัดเข้าไป —
test รันได้โดยไม่ต่อเน็ต ไม่เสียเงิน ไม่ต้องรอ API
นี่คือเหตุผลอันดับหนึ่งที่โค้ด Go ในโปรดักชันใช้ interface
- dependency injection (DI)
- dependency = ของที่โค้ดเราต้องพึ่ง (ที่นี่คือ gateway จ่ายเงิน) · DI =
ส่งมันเข้าไปทางพารามิเตอร์ แทนที่จะให้โค้ดสร้างเองข้างใน ·
NewCheckoutService(gateway)คือ DI แบบง่ายที่สุด · “inject gateway เข้าไปตอนสร้าง service เลยดีกว่าไหม” - coupling / decouple
- ผูกกันแน่นจนแยกไม่ออก / ทำให้ผูกกันหลวมลง · ประโยคที่ reviewer พูดจริง: “ตรงนี้ coupling กับ PayPal แน่นไป แยกเป็น interface ได้ไหม”
- implementation (impl)
- “ตัวจริงที่ทำงานได้” ที่อยู่หลัง interface · “Paypal เป็น implementation หนึ่งของ PaymentGateway”
- stub
- ของปลอมที่คืนค่าที่เราเซ็ตไว้เฉย ๆ ไม่ตรวจอะไรทั้งนั้น ·
FakeGatewayข้างบนที่Pay()คืนnilเฉย ๆ จริง ๆ แล้วคือ stub (ชื่อขึ้นต้นว่า Fake เป็นแค่ธรรมเนียมตั้งชื่อ) - mock
- ของปลอมที่คอยจำว่าถูกเรียกจริงไหม กี่ครั้ง ด้วยค่าอะไร แล้วให้เทสเช็กได้ · “mock ไว้ด้วยว่า Pay ต้องถูกเรียกครั้งเดียวนะ”
- fake
- ของจริงย่อส่วนที่ทำงานได้จริง เช่น DB ที่เก็บใน memory แทนการต่อ Postgres จริง
สามคำนี้วงการแยกกันจริง ๆ — ถ้า reviewer บอกว่า “อันนี้ควรเป็น stub ไม่ใช่ mock” เขากำลังบอกว่า เทสนี้ไม่ควรไปผูกกับจำนวนครั้งที่ถูกเรียก แค่ให้ค่ากลับมาก็พอ
4 · กฎที่ทำให้ interface ไม่พัง: เล็กเข้าไว้
อย่าสร้าง interface ตามทุกสิ่งที่ type ทำได้ ให้สร้างตามสิ่งที่ผู้เรียกจำเป็นต้องใช้
❌ ก้อนใหญ่
type UserService interface {
CreateUser()
UpdateUser()
DeleteUser()
FindUser()
Login()
Logout()
ResetPassword()
SendEmail()
ExportReport()
GenerateInvoice()
}
✅ แยกเล็ก ๆ
type UserFinder interface {
FindUser(id string) (User, error)
}
type UserCreator interface {
CreateUser(user User) error
}
type Authenticator interface {
Login(username, password string) error
}
ปัญหาของก้อนใหญ่: ฟังก์ชันที่แค่อยากอ่านข้อมูล user
ต้องรับ interface ที่มี DeleteUser() ติดมาด้วย
เขียน fake สำหรับ test ทีต้อง implement 10 method ทั้งที่ใช้จริงอันเดียว
และผู้อ่านโค้ดก็เดาไม่ออกว่าฟังก์ชันนี้จะไปทำอะไรกับ user บ้าง
ยื่นกุญแจดอกเดียวของห้องนั้น คนเห็นก็รู้ทันทีว่างานวันนี้แตะแค่ห้องเดียว
ต่างจากยื่นพวงกุญแจทั้งตึกที่ใครก็เดาไม่ออกว่าช่างจะไปทำอะไรบ้าง
UserFinder คือกุญแจดอกเดียว · UserService ก้อนใหญ่คือพวงทั้งตึก
ที่ไม่เหมือนกัน: ช่างที่ได้กุญแจดอกเดียวเข้าห้องอื่นไม่ได้จริง ๆ
แต่ interface เล็กไม่ได้กันใครทำอะไร — ฟังก์ชันที่รับ UserFinder
ยัง type assert (บทเรียน 11)
กลับไปเรียก DeleteUser() ได้ ถ้าของที่ส่งมามี method นั้นติดตัวอยู่ ·
interface เล็กจึงเป็นเรื่องสื่อสารเจตนาและเขียนเทสง่าย ไม่ใช่การกันสิทธิ์
"Accept interfaces, return structs" —
พารามิเตอร์รับเป็น interface (ยืดหยุ่น ใครส่งอะไรมาก็ได้)
แต่ค่าที่คืนเป็น struct จริง ๆ (ผู้เรียกได้ของครบ ไม่ถูกจำกัด)
สังเกต NewCheckoutService ข้างบน: รับ PaymentGateway
แต่คืน *CheckoutService
- interface pollution
- อาการ “สร้าง interface พร่ำเพรื่อ” ทั้งที่มีเจ้าเดียวและไม่ได้ใช้ประโยชน์อะไร · reviewer Go พูดกันจริง: “อันนี้ interface pollution หรือเปล่า”
- ชื่อลงท้ายด้วย -er
- ธรรมเนียมตั้งชื่อ interface ของ Go — ตั้งตามสิ่งที่มันทำ
→
Reader·UserFinder· ถ้าตั้งชื่อไม่ลงตัว มักแปลว่า interface ยังใหญ่ไป
5 · กับดักใหญ่: pointer receiver กับ interface
type Notifier interface {
Notify(message string) error
}
type EmailNotifier struct{}
// สังเกต: pointer receiver
func (e *EmailNotifier) Notify(message string) error {
return nil
}
var n Notifier = EmailNotifier{}
บรรทัดสุดท้าย compile ผ่านไหม?
ไม่ผ่าน — ผมรันทดสอบแล้ว:
cannot use EmailNotifier{} (value of struct type EmailNotifier)
as Notifier value in variable declaration:
EmailNotifier does not implement Notifier
(method Notify has pointer receiver)
กฎ:
func (e *EmailNotifier) Notify()→ เฉพาะ*EmailNotifierที่เป็นNotifierfunc (e EmailNotifier) Notify()→ ทั้งEmailNotifierและ*EmailNotifierใช้ได้
ทำไมถึงไม่สมมาตร — และทำไมมันขัดกับบทเรียน 07:
ตอนเรียก method ตรง ๆ อย่าง e.Notify("hi") compiler เติม
& ให้เองได้ เพราะมันรู้ที่อยู่ของ e
(e เป็นตัวแปรจริงที่มีที่อยู่)
แต่ตอนยัดค่าใส่ interface Go จะก็อปค่านั้นเข้าไปเก็บในกล่อง
interface — ของที่อยู่ในกล่องไม่ใช่ตัวแปรของใคร ไม่มีที่อยู่ให้เอา
& ไปชี้ ถ้า Go ยอมให้ผ่าน method ที่ต้องแก้ค่าจะไปแก้
"ก๊อปปี้ในกล่อง" ซึ่งไม่มีผลกับตัวแปรข้างนอกเลย — เป็นบั๊กเงียบแบบเดียวกับ
value receiver ใน บทเรียน 07
Go จึงเลือกให้ compile ไม่ผ่านไปเลย ดีกว่าปล่อยให้แก้ค่าแล้วหาย
ส่วนทางกลับกัน (value receiver + *T) ทำได้เสมอ
เพราะจาก pointer ย้อนไปเอาค่ามาก็อปได้ตลอด
แก้ได้โดยเขียน var n Notifier = &EmailNotifier{}
(ใส่ &) — นี่คือ error ที่คนเริ่มเขียน Go เจอบ่อยที่สุด
อันหนึ่ง เห็นคำว่า "has pointer receiver" เมื่อไหร่ ให้มองหา
& ที่หายไป
งานบางอย่างที่อำเภอ ต้องใช้บัตรตัวจริงเท่านั้น เพราะเจ้าหน้าที่ต้องไปแก้ข้อมูลในทะเบียน ถ้าเรายื่นแต่สำเนา เขาจะปฏิเสธตั้งแต่ที่เคาน์เตอร์ — เพราะถ้ารับไว้แล้วไปแก้บนสำเนา ตัวจริงจะไม่เปลี่ยนเลย และเราจะกลับบ้านโดยคิดว่าเรื่องเสร็จแล้ว
การเอา ค่า ใส่ interface คือการยื่นสำเนา (Go ก็อปค่านั้นไปเก็บในกล่อง interface) ·
method ที่เขียนเป็น *EmailNotifier คืองานที่ต้องใช้ตัวจริง ·
การใส่ & คือการยื่นตัวจริงให้
ที่ไม่เหมือนกัน: ที่อำเภอปฏิเสธตอนเรายื่นเอกสาร แต่ Go ปฏิเสธ
ตั้งแต่ตอน compile คือก่อนโปรแกรมเริ่มทำงานด้วยซ้ำ ·
และในสายตา Go EmailNotifier กับ *EmailNotifier
เป็น type คนละตัวกันจริง ๆ ที่ถือ method ติดตัวไม่เท่ากัน
ที่สำคัญกว่านั้น: ที่อำเภอเขาดูว่างานนั้นต้องแก้ทะเบียนจริงไหม
แต่ Go ไม่ได้อ่านข้างใน method เลย — Notify() ข้างบนแค่
return nil ไม่ได้แก้อะไรสักบรรทัด ก็ยัง compile ไม่ผ่านเหมือนเดิม
เพราะ Go ดูอย่างเดียวว่าเขียน receiver เป็น *EmailNotifier หรือเปล่า ·
เรื่อง “แก้แล้วหาย” คือที่มาของกฎ ไม่ใช่เงื่อนไขที่ compiler ไล่ตรวจรายครั้ง
- value receiver / pointer receiver
- ตัวที่อยู่ในวงเล็บหน้าชื่อ method ·
(e EmailNotifier)= value receiver (ทำงานกับสำเนา) ·(e *EmailNotifier)= pointer receiver (ทำงานกับตัวจริง) · ถามว่า “method นี้ใช้ receiver แบบไหน” ดีกว่าถามว่า “ตัวนี้มีดาวหรือเปล่า” - method set
- “รายชื่อ method ที่ type หนึ่งถือติดตัวไปได้” — compiler ใช้รายชื่อนี้ตัดสินว่าครบตาม interface ไหม ·
“method set ของ
EmailNotifierมีNotifyไหม” - addressable
- “มีที่อยู่ให้เอา
&ไปชี้ได้ไหม” · ตัวแปรทั่วไป addressable แต่ค่าที่ถูกก็อปเข้าไปอยู่ในกล่อง interface แล้วไม่ addressable — นี่คือรากของกฎทั้งข้อนี้ - compile-time error
- error ที่เจอตอนแปลโค้ด ยังไม่ทันได้รัน (ตรงข้ามกับ runtime error ที่พังตอนโปรแกรมทำงานอยู่) ·
“has pointer receiver” เป็น compile-time error — เห็นเมื่อไหร่ให้มองหา
&ที่หาย
6 · ทดสอบความจำ
1. type ใน Go ประกาศว่าตัวเองทำตาม interface ยังไง?
Go ใช้ structural typing — compiler ดูแค่ว่า method ครบตาม signature ไหม ผลพลอยได้คือคุณเขียน interface มาครอบ type ของ library คนอื่นได้ โดยที่เขาไม่ต้องรู้เรื่องด้วยเลย
2. method ใช้ pointer receiver — เอา value ใส่ interface ได้ไหม?
"method set" คือรายชื่อ method ที่ type หนึ่ง ๆ ถือติดตัวไปได้ — compiler ใช้รายชื่อนี้ตัดสินว่า type นั้นครบตาม interface ไหม
EmailNotifier(ค่า) ถือได้เฉพาะ method ที่เขียนfunc (e EmailNotifier)*EmailNotifier(pointer) ถือได้ทั้งสองแบบ — ทั้งที่เขียนfunc (e EmailNotifier)และfunc (e *EmailNotifier)
พอ Notify เขียนเป็น
func (e *EmailNotifier) มันจึงอยู่ในรายชื่อของ
*EmailNotifier เท่านั้น ตัวค่าเปล่า ๆ จึงยังไม่เป็น
Notifier ต้องใส่ &
3. ทำไม interface เล็ก ๆ ถึงดีกว่า interface ก้อนใหญ่?
ไม่เกี่ยวกับความเร็วหรือหน่วยความจำเลย และ Go ก็ไม่ได้บังคับ —
มันเป็นเรื่องของการออกแบบ interface ที่เล็กทำให้ signature
ของฟังก์ชันบอกความจริงว่ามันแตะอะไรบ้าง และเขียน fake สำหรับ test ได้ง่าย
ดู io.Reader ใน standard library เป็นตัวอย่าง: มี method เดียว
แต่ทั้งภาษาใช้มัน
4. สำนวน "Accept interfaces, return structs" แปลว่าอะไร?
พารามิเตอร์เป็น interface → ใครมี method ครบก็ส่งเข้ามาได้ ยืดหยุ่นสูงสุด · ค่าคืนเป็น struct → ผู้เรียกได้ field และ method ครบ ไม่ถูกบีบให้เห็นแค่บางส่วน ถ้าคืนเป็น interface ผู้เรียกจะเข้าถึง อะไรเพิ่มไม่ได้เลยโดยไม่ทำ type assertion
7 · ลงมือทำ
ภารกิจ: ระบบแจ้งเตือนที่เปลี่ยนช่องทางได้ (workshop จากสไลด์)
บริษัทต้องการระบบส่งข้อความแจ้งเตือน ส่งผ่าน Email หรือ
LINE ก็ได้ — ใช้ interface ให้ฟังก์ชัน SendAlert
ทำงานกับหลายช่องทางได้
- ประกาศ
type Alerter interface { Send(message string) error } - สร้าง
EmailAlerterและLineAlerterที่ implement มัน — พิมพ์"Email: ..."และ"LINE: ..."ตามลำดับ - เขียน
func SendAlert(a Alerter, message string) error - เรียกให้ได้ผลลัพธ์:
Email: Server CPU is high LINE: Database connection failed - ท้าทาย 1: ทำให้ส่งพร้อมกันทุกช่องทางได้ —
คำใบ้:
func SendAll(alerters []Alerter, message string) - ท้าทาย 2: เขียน
FakeAlerterที่เก็บ ข้อความไว้ใน slice แทนที่จะพิมพ์ แล้วใช้มันตรวจว่าSendAllส่งครบทุกช่องทางจริงไหม — นี่คือรูปแบบของ unit test ที่คุณจะเขียนจริงในสัปดาห์หน้า
รู้ได้ยังไงว่าถูก: หลังเรียกSendAllแล้วlen(fake.messages)ต้องเท่ากับจำนวนครั้งที่ส่ง
⚠️ ถ้าได้0ทั้งที่ไม่มี error เลย — นั่นไม่ใช่บั๊กของSendAllย้อนไปดู บทเรียน 07:func (f FakeAlerter) Send(...)ได้ copy มาappendลงไปก็หายพร้อม copy ต้องใช้func (f *FakeAlerter) Send(...)แล้วส่ง&FakeAlerter{}เข้า slice ของAlerter - ท้าทาย 3: ลองเปลี่ยน receiver ของ
EmailAlerter.Sendเป็น pointer (func (e *EmailAlerter) Send(...)) แล้วรันใหม่ — พังตรงไหน แล้วแก้ยังไง?
อ่านต่อจากต้นทาง
แหล่งหลักที่แนะนำ: A Tour of Go — Interfaces ถึงหน้า 14 — กดรันแก้โค้ดได้ในเว็บเลย ประมาณ 15 นาทีจบ พอเข้าใจแล้วค่อยอ่าน Go Code Review Comments — Interfaces ซึ่งบอกกฎที่ reviewer จริง ๆ ใช้ ข้อที่สำคัญที่สุดสรุปได้ว่า ให้ประกาศ interface ไว้ใน package ของฝั่งที่ใช้ ไม่ใช่ฝั่งที่ implement — ตรงข้ามกับสัญชาตญาณของคนมาจากภาษา OOP แต่มันคือสิ่งที่ทำให้ผู้ใช้กำหนดได้เองว่าต้องการ method อะไรบ้าง
💬 ติดตรงไหนถามได้เลย — ท้าทายข้อ 3 (pointer receiver) ถ้า error ยาวจนอ่านไม่ออกว่ามันบ่นเรื่องอะไร ส่งมาได้เลย มันเป็น error ที่คนเขียน Go ใหม่ ๆ เจอทุกคน
อ้างอิง
- A Tour of Go — Interfaces — แบบฝึกหัดรันได้ในเบราว์เซอร์
- Go spec — Interface types — นิยามและกฎ method set
- Go spec — Method sets — ที่มาของกฎ pointer receiver
- Go Code Review Comments — Interfaces — interface ควรอยู่ package ไหน
- Effective Go — Interfaces and methods