มือใหม่หัด Go: Factory Pattern
Tags: go, designpatterns, beginners
Published: 2023-05-16 07:06:58

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

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


![diagram](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gt54vo71v97vgvpblcyr.png)

## Product interface
ขั้นตอนแรกให้สร้าง `IWeapon` เป็น interface ที่ระบุว่าอาวุธของเราควรจะมี method อะไรบ้าง
```go
type IWeapon interface {
	setName(name string)
	setPower(power int)
	getName() string
	getPower() int
	attack()
}
```

## Concrete product
สร้าง `Weapon` เป็น struct ที่ implement `IWeapon` 
```go
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 อีกทอดหนึ่ง
```go
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 ประเภทต่างๆ ในที่นี่จะมีการตั้งชื่อ 
```go
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()` ได้ด้วย
```go
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:
```go
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:
- [Factory Method in Go](https://refactoring.guru/design-patterns/factory-method/go/example)
- [Factory Design Pattern in Go (Golang) - Welcome To Golang By Example](https://golangbyexample.com/golang-factory-design-pattern/)