設計模式 in Go:Prototype

建造模式,處理與創建對象及對象實例化過程相關的問題,通常尋求以分離業務代碼和對象創建邏輯,或將複雜的構造邏輯封裝在可重用組件中的方式。

讓我們來探討第四種創建模式——原型模式。

問題背景:
我們有一個對象,你要創建它的一個拷貝。您可以採用什麼方法來實現這一點?也許您需要知道該對象所屬的類型,從而能夠創建新的同樣類型的對象,然後將原始對象中每個字段值複製到新對象中。

  1. 該類包含一些隱藏字段,因此你無法複製字段的值。

  2. 或者,您可能無法知道對象的具體類型,因爲您只通過接口引用到這個類;在這種情況下,您就不能創建一個與原始類相同的新對象。

  3. 或者,該對象可能非常複雜,僅僅將所有字段複製到新對象並不那麼方便

  4. 即使我們知道對象的類和能複製所有字段的值,也會遇到另一個問題:你的代碼需要依賴於該對象的類型,這可能會引起您的焦慮。

解決方案:

原型模式是一種創建者設計模式,它讓您能夠複製現有對象而無需將代碼與對象的類型綁定。

該對象的類可以實現 clone() 方法,並且所有字段值的類型也可以實現 clone() 方法。這樣當您調用 clone() 方法時,它將通過遞歸地迭代每個字段來複制對象。

原型模式能夠降低複製對象的複雜性,同時你的代碼也與對象類型解耦開了。

例如,有輛汽車,我們想要克隆它,我說的是完全複製那輛汽車的框架、輪子、門、胎管、管道,即使是一根微小螺絲,我們都要完全克隆。如果他們給我每個組件的詳細說明,如何克隆每個部分,我可以安全地做到。

這正是原型模式所告訴我們的。如果你想深度複製一個對象,你最好讓每個子對象實現 clone()。

變體模式:
即使原型模式本身沒有變體,但它與其他模式有關係,也許可以看作是原型的變體或擴展。

  1. 原型管理器,這是原型模式的一個變體,它涉及到一箇中央管理器或 registry,存儲和管理一組原型對象。客戶端可以向管理器請求一個新的對象,並指定一個關鍵或標識符,管理器將克隆並返回相應的原型對象。這在需要集中管理多種類型對象時非常有用。

  2. 深度複製 vs 淺度複製 在原型模式中克隆對象時,有兩種方法:深度複製和淺度複製。深度複製中,原始對象的所有屬性和子對象也會被克隆,從而創建一個完全獨立的副本。淺度複製中,只是原始對象的頂層屬性被複制,而子對象之間則共享原始對象和克隆對象之間的關係。選擇深度複製或淺度複製取決於應用程序的特定要求。

  3. 序列化和反序列化 原型模式的一個變體涉及將對象 serialize 到字節流或字符串表示,然後反序列化以創建一個新的對象。這是當對象需要通過網絡傳輸或存儲在持久存儲介質時非常有用的。serialize 和 deserialize 過程實際上創建了一個與原始對象 identical 的新對象。

關係:
許多設計模式從工廠模式開始(較爲簡單和定製化),然後演進到抽象工廠、原型或建造者模式(更靈活但更復雜)。

優點:

  1. 靈活性:輕鬆地創建具有變體的現有對象。

  2. 效率:克隆對象比從頭構建它們快得多。

  3. 代碼重用:原型模式作爲創建新對象的模板,鼓勵代碼重用。

  4. 適合大對象:克隆複雜對象變得可管理。

缺點:

  1. 限制的靈活性:直接修改原型將影響所有克隆。

  2. 潛在的內存問題:如果不加以控制,過度克隆可能導致內存泄露。

  3. 調試複雜性:由於原型和克隆之間的關係,調試變得更難。

  4. 對簡單場景太複雜:對於簡單對象創建,不總是最有效的解決方案。

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