Factory Pattern เป็น creational design pattern แบบหนึ่ง โดยทั่วไปแล้วเป็นรูปแบบที่ใช้กันอย่างแพร่หลายใน Object Oriented Programming แม้ภาษา Go จะไม่ได้มี OOP feature มากมายขนาดนั้น แต่เราสามารถนำมาปรับใช้ในแบบ Simple Factory ได้
บทความนี้ จะพามาสร้างโรงงานอาวุธสไตล์เกม RPG โดยใช้แนวคิด Simple Factory มาเขียนโค้ดกัน
Product interface
ขั้นตอนแรกให้สร้าง IWeapon
เป็น interface ที่ระบุว่าอาวุธของเราควรจะมี method อะไรบ้าง
type IWeapon interface {
setName(name string)
setPower(power int)
getName() string
getPower() int
attack()
}
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() {
}
สร้าง 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())
}
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
}
}
สังเกตการคืนค่าเป็น &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()
}
output:
Excalibur Slash! 9000
LongBow Shot! 100
สรุป 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()
}
References:
Top comments (0)