มือใหม่หัด Go: Factory Pattern

nattrio

Nattrio

Posted on May 16, 2023

มือใหม่หัด Go: Factory Pattern

Factory Pattern เป็น creational design pattern แบบหนึ่ง โดยทั่วไปแล้วเป็นรูปแบบที่ใช้กันอย่างแพร่หลายใน Object Oriented Programming แม้ภาษา Go จะไม่ได้มี OOP feature มากมายขนาดนั้น แต่เราสามารถนำมาปรับใช้ในแบบ Simple Factory ได้

บทความนี้ จะพามาสร้างโรงงานอาวุธสไตล์เกม RPG โดยใช้แนวคิด Simple Factory มาเขียนโค้ดกัน

diagram

Product interface

ขั้นตอนแรกให้สร้าง IWeapon เป็น interface ที่ระบุว่าอาวุธของเราควรจะมี method อะไรบ้าง

type IWeapon interface {
    setName(name string)
    setPower(power int)
    getName() string
    getPower() int
    attack()
}
Enter fullscreen mode Exit fullscreen mode

Concrete product

สร้าง Weapon เป็น struct ที่ implement IWeapon

type Weapon struct {
    name  string
    power int
}

func (w *Weapon) setName(name string) {
    w.name = name
}

func (w *Weapon) setPower(power int) {
    w.power = power
}

func (w *Weapon) getName() string {
    return w.name
}

func (w *Weapon) getPower() int {
    return w.power
}

func (w *Weapon) attack() {
}
Enter fullscreen mode Exit fullscreen mode

สร้าง concrete Sword และ Bow ที่ struct ที่ทำการ embed Weapon struct ซึ่งจะเป็นการ implement method ทั้งหมดของ IWeapon แบบ indirect อีกทอดหนึ่ง

type Sword struct {
    Weapon
}

func (s *Sword) attack() {
    fmt.Printf("%s Slash! %d\n", s.getName(), s.getPower())
}

type Bow struct {
    Weapon
}

func (b *Bow) attack() {
    fmt.Printf("%s Shot! %d\n", b.getName(), b.getPower())
}
Enter fullscreen mode Exit fullscreen mode

Factory

สร้าง WeaponFactory เป็น struct ที่มี method createWeapon() รับค่า string เพื่อสร้าง Weapon ประเภทต่างๆ ในที่นี่จะมีการตั้งชื่อ

type WeaponFactory struct {
}

func (wf *WeaponFactory) createWeapon(weaponType string) IWeapon {
    switch weaponType {
    case "Sword":
        return &Sword{Weapon{"Sword", 100}}
    case "Bow":
        return &Bow{Weapon{"Bow", 50}}
    default:
        return nil
    }
}
Enter fullscreen mode Exit fullscreen mode

สังเกตการคืนค่าเป็น &Sword{Weapon{}} และ &Bow{Weapon{}} การใช้ & เพราะว่า Sword และ Bow types นั้น embed Weapon struct โดยตัว factory method เองต้อง return เป็น pointer (memory address) ซึ่งใช้ในการสร้าง instance ออกมา อีกทั้งเป็นการจัดการ memory ที่มีประสิทธิภาพโดยลดการ copy struct อย่างไม่จำเป็น

Client code

ได้เวลาสร้างอาวุธจริงๆ ขึ้นมาแล้ว โดยเราจะสร้าง Sword และ Bow อย่างละชิ้น ซึ่งต้องทำผ่าน weaponFactory.createWeapon() อาวุธที่ได้ออกมาสามารถตั้งชื่อใหม่ เปลี่ยนค่าพลังได้ และนำไปใช้ Attack() ได้ด้วย

func main() {
    weaponFactory := WeaponFactory{}

    Excailbur := weaponFactory.createWeapon("Sword")
    Excailbur.SetName("Excalibur")
    Excailbur.SetPower(9000)
    Excailbur.Attack()

    LongBow := weaponFactory.createWeapon("Bow")
    LongBow.SetName("LongBow")
    LongBow.SetPower(100)
    LongBow.Attack()
}
Enter fullscreen mode Exit fullscreen mode

output:

Excalibur Slash! 9000
LongBow Shot! 100
Enter fullscreen mode Exit fullscreen mode

สรุป Factory Pattern

ข้อดี

  • Encapsulation และมีการแยก separation of concern
  • Abstraction: client มีการดำเนินการกับ interface ซึ่ง decouple จาก implementation
  • Flexibility: สามารถปรับหรือเพิ่ม concrete ชนิดใหม่ได้โดยไม่ต้องยุ่ง client code

ข้อควรระวัง

  • เป็นการเพิ่มความซับซ้อนที่มากขึ้นเมื่อเทียบกับการสร้าง instance แบบตรงๆ

All code:

package main

import "fmt"

type IWeapon interface {
    SetName(name string)
    SetPower(power int)
    GetName() string
    GetPower() int
    Attack()
}

type Weapon struct {
    name  string
    power int
}

func (w *Weapon) SetName(name string) {
    w.name = name
}

func (w *Weapon) SetPower(power int) {
    w.power = power
}

func (w *Weapon) GetName() string {
    return w.name
}

func (w *Weapon) GetPower() int {
    return w.power
}

func (w *Weapon) Attack() {
}

type Sword struct {
    Weapon
}

func (s *Sword) Attack() {
    fmt.Printf("%s Slash! %d\n", s.GetName(), s.GetPower())
}

type Bow struct {
    Weapon
}

func (b *Bow) Attack() {
    fmt.Printf("%s Shot! %d\n", b.GetName(), b.GetPower())
}

type WeaponFactory struct {
}

func (wf *WeaponFactory) createWeapon(weaponType string) IWeapon {
    switch weaponType {
    case "Sword":
        return &Sword{Weapon{"Sword", 100}}
    case "Bow":
        return &Bow{Weapon{"Bow", 50}}
    default:
        return nil
    }
}

func main() {
    weaponFactory := WeaponFactory{}

    Excalibur := weaponFactory.createWeapon("Sword")
    Excalibur.SetName("Excalibur")
    Excalibur.SetPower(9000)
    Excalibur.Attack()

    LongBow := weaponFactory.createWeapon("Bow")
    LongBow.SetName("LongBow")
    LongBow.SetPower(100)
    LongBow.Attack()
}
Enter fullscreen mode Exit fullscreen mode

References:

💖 💪 🙅 🚩
nattrio
Nattrio

Posted on May 16, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related