DEV Community

Pallat Anchaleechamaikorn
Pallat Anchaleechamaikorn

Posted on

Composition Over Inheritance in Go

#go

เมื่อเราพูดถึงประโยคนี้

Composition Over Inheritance

ในด้านของคนที่เขียน Go เอาจริงๆ ถ้าจะให้ตอบแบบห้วนๆเลยก็คือ เนื่องจาก Go ไม่ใช่ภาษา OOP มันเลยไม่มีการทำ Inheritance แต่สามารถใช้ Composition มาทดแทนได้

ทีนี้ก็ต้องมาดูกันว่า แล้วเราต้องการอะไรจากการทำ Inheritance ก่อน จะได้เอา Composition มาเทียบว่าแทนกันได้จริงหรือไม่ ซึ่งหลักๆเท่าที่รู้ก็จะเป็นเรื่อง

  • ลด duplicated code
  • reusability ไม่ต้องเขียนโค้ดซ้ำ โดยเฉพาะตัว base จะไม่ถูกแก้ไข
  • extensibility เอาไปต่อเติมเพิ่มขยายได้โดยใช้ base เดิมมาตั้งต้น

เอาแค่นี้ก่อน เพราะถ้ามากกว่านี้เราจะไป 4 pillars ก็ละ 😅

เรามาดูท่ากันก่อนว่า composition ใน Go เขาทำกันอย่างไร

type Context struct {
  kv map[string]string
}

func (c *Context) Get(k string) string {
    return c.kv[k]
}
Enter fullscreen mode Exit fullscreen mode

สมมุติว่าเรามี type ชื่อ Context และ type นี้มี 1 เมธอดชื่อ Get โดยมันจะสามารถดึงเอาค่า key/value ออกมาจาก Context ได้ด้วยการให้ key ลงไป

ทีนี้เราอยากสร้าง BusinessContext มาใช้งาน โดยมันจะมีความสามารถเดิมของ Context อยู่ด้วยแบบนี้

type BusinessContext struct {
    Context
}

func (c *BusinessContext) Name() string {
    return c.Context.Get("name")
}
Enter fullscreen mode Exit fullscreen mode

ข้อสังเกตการใช้ c.Context.Get คือการเรียก .Get ผ่าน embedded field

จากตัวอย่าง เราเรียกว่า embedded field คือการ embed type Context เข้าไปใน BusinessContext ซึ่งการทำแบบนี้ จะทำให้ instance ของ BusinessContext จะยังสามารถใช้ .Get ได้เหมือนเดิม เช่น

ctx := &Context{kv:map[string]string{"name":"Pallat"}}
bctx := &BusinessContext{ctx}

println(bctx.Get("name"))
println(bctx.Name())
Enter fullscreen mode Exit fullscreen mode

จะเห็นว่าเรายังสามารถเรียกใช้ .Get ผ่าน bctx ได้เหมือนใช้ ctx.Get และสิ่งที่เพิ่มเติมเข้าไปคือสามารถใช้ .Name ได้ด้วย

แต่ก็มีข้อควรระวังในการทำนิดหนึ่งคือว่า กรณีที่เราจะทำการ Override ของเดิมเช่น Get

func (c *BusinessContext) Get(k string) string {
    return c.Get(k) + "."
}
Enter fullscreen mode Exit fullscreen mode

แบบนี้จะมีปัญหา เพราะในฟังก์ชั่น Get ที่เราเขียนขึ้นมาใหม่ มันมีเรียก c.Get โดยไม่ได้ระบุชื่อ field แบบนี้ c.Context.Get อันที่จริงมันเขียนแบบนี้ได้ แต่มันจะทำงานจริงไม่ได้ เพราะ c.Get มันเหมือนเรียกตัวเองแบบ recursive

ถ้าต้องการเรียก .Get จากตัว base ที่เรา embedded เข้ามา ต้องระบุ c.Context.Get ลงไปตรงๆ โดยเฉพาะเมื่อเรามีการ override method ซึ่งถ้าจะให้ดี ก็ควรระบุชื่อ embedded เสมอนั่นเองครับ เพื่อความปลอดภัย

Discussion (0)