Prototype Pattern,創建物件不再從頭開始浪費時間

什麼是 Prototype Pattern?

將物件以拷貝的方式建立

如果某類物件在初始化時,會消耗大量的成本,例如:「對 DB 做 query、讀取 json file、讀取使用者滑鼠的軌跡」,那在建立完此物件後,又須建立此此類相似的物件時,我們會希望用拷貝(copy)的,以避免初始化的成本。

而拷貝這件事,又有分深拷貝(deep copy)淺拷貝(shallow copy),如果是有 javascript 經驗的開發者一定很熟悉這兩個詞,在 golang 他們主要是:

  • 深拷貝(deep copy):當拷貝時是拷貝數據本身,所以對拷貝的數據修改,不會影響到原本的數據,Int、Float、String、Bool、Struct、Array 都採用這類複製
  • 淺拷貝(shallow copy):當拷貝時是拷貝數據指標,所以對拷貝的數據修改,會影響到原本的數據,Slice、Map 都採用這類複製

使用淺拷貝通常是為了更節省記憶體,但如果複製出來的數據有要完全獨立的需求得採用深拷貝,

小節一下 Prototype Pattern 的優缺點:

優點:

  • 不需要初始化的成本

缺點:

  • 需要注意深淺拷貝的差異,不然有可能會修改到原始的數據

問題情境

Sony 已經做出了 PS5 原型機,在這個過程中有經歷「建模、分析硬體數據、效能測試」等動作,但現在要量產 PS5,並不需要這些浪費時間的動作,也不需要使用原型的組件來量產。

解決方式

相關的 code 在Github - go-design-patterns

package main

import (
	"fmt"
	"time"
)

type PS5 struct {
	Version string
	CPU     []string
	GPU     map[string]string
}

func (_ PS5) Modeling() {
	time.Sleep(time.Second) // 模擬耗時
}

func (_ PS5) LoadTest() {
	time.Sleep(time.Second) // 模擬耗時
}

func (_ PS5) Analysis() {
	time.Sleep(time.Second) // 模擬耗時
}

func (p *PS5) Clone() *PS5 {
	// 將基本物件拷貝
	ps5 := PS5{}
	ps5 = *p

	// 由於Slice是淺拷貝,所以必須透過copy方法來把深層元素拷貝
	cpu := make([]string, len(p.CPU))
	copy(cpu, p.CPU)
	ps5.CPU = cpu

	// 由於Map是淺拷貝,所以需把元素一個一個拿出來拷貝
	gpu := make(map[string]string)
	for k, v := range p.GPU {
		gpu[k] = v
	}
	ps5.GPU = gpu

	return &ps5
}

func CreatePrototypePS5() *PS5 {
	prototypePS5 := PS5{
		Version: "Prototype",
		CPU:     []string{"原型CPU"},
		GPU:     make(map[string]string),
	}
	prototypePS5.GPU["GPU"] = "原型GPU"
	prototypePS5.Modeling()
	prototypePS5.LoadTest()
	prototypePS5.Analysis()
	return &prototypePS5
}

func main() {
	prototypePS5 := CreatePrototypePS5()
	ps5 := prototypePS5.Clone()
	ps5.Version = "Version-1"
	ps5.CPU[0] = "量產CPU"
	ps5.GPU["GPU"] = "量產GPU2"
	fmt.Println(prototypePS5)
	fmt.Println(ps5)
}

範例中PS5{}.CPUPS5{}.GPU分別是 Slice 與 Map,由於都是淺拷貝的數據,所以需要手動以copy()與 for 迴圈來將物件元素一個一個複製,而PS5{}.Version就不需要手動複製,因為是深拷貝的數據形態

comments powered by Disqus