設計模式 in Go:Prototype
建造模式,處理與創建對象及對象實例化過程相關的問題,通常尋求以分離業務代碼和對象創建邏輯,或將複雜的構造邏輯封裝在可重用組件中的方式。
讓我們來探討第四種創建模式——原型模式。
問題背景:
我們有一個對象,你要創建它的一個拷貝。您可以採用什麼方法來實現這一點?也許您需要知道該對象所屬的類型,從而能夠創建新的同樣類型的對象,然後將原始對象中每個字段值複製到新對象中。
-
該類包含一些隱藏字段,因此你無法複製字段的值。
-
或者,您可能無法知道對象的具體類型,因爲您只通過接口引用到這個類;在這種情況下,您就不能創建一個與原始類相同的新對象。
-
或者,該對象可能非常複雜,僅僅將所有字段複製到新對象並不那麼方便
-
即使我們知道對象的類和能複製所有字段的值,也會遇到另一個問題:你的代碼需要依賴於該對象的類型,這可能會引起您的焦慮。
解決方案:
原型模式是一種創建者設計模式,它讓您能夠複製現有對象而無需將代碼與對象的類型綁定。
該對象的類可以實現 clone() 方法,並且所有字段值的類型也可以實現 clone() 方法。這樣當您調用 clone() 方法時,它將通過遞歸地迭代每個字段來複制對象。
原型模式能夠降低複製對象的複雜性,同時你的代碼也與對象類型解耦開了。
例如,有輛汽車,我們想要克隆它,我說的是完全複製那輛汽車的框架、輪子、門、胎管、管道,即使是一根微小螺絲,我們都要完全克隆。如果他們給我每個組件的詳細說明,如何克隆每個部分,我可以安全地做到。
這正是原型模式所告訴我們的。如果你想深度複製一個對象,你最好讓每個子對象實現 clone()。
變體模式:
即使原型模式本身沒有變體,但它與其他模式有關係,也許可以看作是原型的變體或擴展。
-
原型管理器,這是原型模式的一個變體,它涉及到一箇中央管理器或 registry,存儲和管理一組原型對象。客戶端可以向管理器請求一個新的對象,並指定一個關鍵或標識符,管理器將克隆並返回相應的原型對象。這在需要集中管理多種類型對象時非常有用。
-
深度複製 vs 淺度複製 在原型模式中克隆對象時,有兩種方法:深度複製和淺度複製。深度複製中,原始對象的所有屬性和子對象也會被克隆,從而創建一個完全獨立的副本。淺度複製中,只是原始對象的頂層屬性被複制,而子對象之間則共享原始對象和克隆對象之間的關係。選擇深度複製或淺度複製取決於應用程序的特定要求。
-
序列化和反序列化 原型模式的一個變體涉及將對象 serialize 到字節流或字符串表示,然後反序列化以創建一個新的對象。這是當對象需要通過網絡傳輸或存儲在持久存儲介質時非常有用的。serialize 和 deserialize 過程實際上創建了一個與原始對象 identical 的新對象。
關係:
許多設計模式從工廠模式開始(較爲簡單和定製化),然後演進到抽象工廠、原型或建造者模式(更靈活但更復雜)。
優點:
-
靈活性:輕鬆地創建具有變體的現有對象。
-
效率:克隆對象比從頭構建它們快得多。
-
代碼重用:原型模式作爲創建新對象的模板,鼓勵代碼重用。
-
適合大對象:克隆複雜對象變得可管理。
缺點:
-
限制的靈活性:直接修改原型將影響所有克隆。
-
潛在的內存問題:如果不加以控制,過度克隆可能導致內存泄露。
-
調試複雜性:由於原型和克隆之間的關係,調試變得更難。
-
對簡單場景太複雜:對於簡單對象創建,不總是最有效的解決方案。
Source:
/////////////////////////// factory.go /////////////////////////////
package prototype
// interface{} is discovered not by designed in advanced, here
// `type Factory struct{}` is ok.
//
// if we will create different kinds of cars, we may need different factories,
// then we'll need an `type Factory interface{}` to specify the behavior of a
// factory, and add subclasses to implement this interface.
type Factory struct{}
func (f *Factory) Create() Car {
c := new(car)
c.Frame = &frame{
doors: [2]Door{
&door{vetexes: []vertex{{0, 0, 5}, {0, 100, 5}, {100, 100, 5}, {0, 100, 5}}},
&door{vetexes: []vertex{{0, 0, 5}, {0, 100, 5}, {100, 100, 5}, {0, 100, 5}}},
},
}
for i := 0; i < len(c.Wheels); i++ {
c.Wheels[i] = &wheel{
tire: &tire{},
tube: &tube{},
}
}
//...
return c
}
////////////////////////////// car.go ////////////////////////////////
package prototype
type Direction int
const (
SteerLeft Direction = iota
SteerRight
)
type Car interface {
Accelerate()
Brake()
Steer(Direction)
Clone() Car
}
type car struct {
Frame Frame
Wheels [4]Wheel
//Engine
//Lights
//FuelTank
//...
}
func (c *car) Accelerate() {
panic("not implemented")
}
func (c *car) Brake() {
panic("not implemented")
}
func (c *car) Steer(_ Direction) {
panic("not implemented")
}
func (c *car) Clone() Car {
nc := new(car)
// clone frame
nc.Frame = c.Frame.Clone()
// clone wheels
nc.Wheels[0] = c.Wheels[0].Clone()
nc.Wheels[1] = c.Wheels[1].Clone()
nc.Wheels[2] = c.Wheels[2].Clone()
nc.Wheels[3] = c.Wheels[3].Clone()
// clone others like engine, fuel tank, lighs
// ...
return nc
}
///////////////////////////// frame.go //////////////////////////////
package prototype
type Frame interface {
OpenTheDoor()
CloseTheDoor()
Clone() Frame
}
type frame struct {
doors [2]Door
//windows [3]Window
}
func (f *frame) OpenTheDoor() {
for i := 0; i < len(f.doors); i++ {
f.doors[i].Open()
}
}
func (f *frame) CloseTheDoor() {
for i := 0; i < len(f.doors); i++ {
f.doors[i].Close()
}
}
func (f *frame) Clone() Frame {
nf := new(frame)
nf.doors[0] = f.doors[0].Clone()
nf.doors[1] = f.doors[1].Clone()
return nf
}
type Door interface {
Open()
Close()
Clone() Door
}
type door struct {
vetexes []vertex
}
func (d *door) Open() {
}
func (d *door) Close() {
}
func (d *door) Clone() Door {
nd := new(door)
vv := make([]vertex, len(d.vetexes))
vv = append(vv[0:0], d.vetexes...)
nd.vetexes = vv
return nd
}
type vertex struct {
x, y float32
thickness float32
}
/////////////////////////////// wheel.go //////////////////////////////
package prototype
type Wheel interface {
Spin()
Brake()
Clone() Wheel
}
type wheel struct {
tire *tire
tube *tube
}
type tire struct{}
type tube struct{}
func (w *wheel) Spin() {
}
func (w *wheel) Brake() {
}
func (w *wheel) Clone() Wheel {
nw := new(wheel)
nw.tire = new(tire)
nw.tube = new(tube)
return nw
}
現在來看下測試:
package prototype_test
import (
"prototype"
"testing"
"github.com/davecgh/go-spew/spew"
)
func TestPrototype(t *testing.T) {
f := new(prototype.Factory)
car1 := f.Create()
spew.Dump(car1)
car2 := car1.Clone()
spew.Dump(car2)
}
ps:此處我們優先考慮可讀性,不會太關注編碼標準,如註釋、camelCase 類型名等。我們將多個文件的代碼組織到一個 codeblock 中僅僅是爲了方便閱讀,如果您想測試可以通過 git 下載源碼 github.com/hitzhangjie/go-patterns。
本文我們學習了 Prototype 模式如何解決生成(複雜)對象拷貝的問題,在 Java 相關的項目或者框架中,經常看到 obj.clone() 這樣的寫法 :)
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/DIz-lI0uJ3zH0st6Noyl_Q