設計模式-創建模式之Prototype(Clone)

更多請移步我的博客

目的

Prototype(也叫做Clone)是創建型模式的一種,允許你通過複製現有的對象來生成新的對象,而不會影響現有對象的內部。

問題

你有一個對象並且想要創建一個副本。你該怎麼做?首先,你需要創建同樣class的一個新對象。然後,你必須遍歷源對象的所有字段並把值拷貝到新對象中。

但是這麼做有一個問題。不是所有對象都可以通過這種方式被拷貝。一些對象擁有無法從外部訪問的私有字段。

這麼做還有另外的問題。因爲你必須知道對象類能夠被遍歷的所有字段,你的代碼需要依賴你要拷貝對象的類。在你僅知道拷貝對象的接口時無法拷貝對象。

解決

Prototype模式把克隆操作委託給對象自己。

他爲所有需要克隆的對象聲明通用的接口。他允許在不緊密耦合具體類的情況下克隆對象。通常,prototype接口只包含一個clone方法。

所有類對clone方法的實現都很像。這個方法創建一個當前類的對象,然後把他的字段值拷貝到新對象中。大多數編程語言都允許訪問同一個類對象中的私有字段,所以這種拷貝過程比較直接。

可以被克隆的對象叫做prototype(原型)。有時,特別是當你的對象有很多字段並且一些字段可配置,原型可以作爲子類的一種替代。在這種情況下,程序提前創建一些原型,然後克隆它們而不是從頭開始重建對象。

真實世界的類比

工業和細胞分裂

在真實生活中,原型在產品量產之前用來做各種測試。然而,在這種情況下,原型並沒有參與真正的生產,是一個被動的角色。

因爲工業原型自己不會拷貝,對這個模式更相近的類比是有絲細胞分裂(生物學,還記得嗎?)。在這種分裂後,兩個完全一樣的細胞就形成了。源細胞就是原型,在創建拷貝中是一個主動的角色。

結構

基本實現

basicStructure

原型註冊實現

registryStructure

  1. Prototype聲明克隆接口。大多情況下,只要一個clone方法就夠了。

  2. Concrete(具體的)Prototype來實現克隆方法。除了直接告杯字段值到新對象外,這個方法也可以解決一些應當對客戶端隱藏的警告。比如,克隆引用對象,解開遞歸依賴等。

  3. 客戶端使用Prototype接口來檢索一個對象的克隆。

  4. Prototype registry提供了對常用原型的簡單訪問,存儲預先創建的一組對象,隨時可以複製。通常,它可以用簡單的(name->prototype)哈希映射實現。但是爲了方便,任何其他搜索條件都可以添加到註冊表中。

僞代碼

在這個例子中,原型被用來克隆代表幾何形狀的對象,而不耦合到它們的類。

所有的形狀類都實現了只有克隆方法的通用克隆接口。子類先調用父類的克隆方法,然後把他們自己的字段拷貝到結果對象中。

因此,原型模式允許客戶端代碼克隆對象,即使不知道和獨立於其特定的類。

// Base prototype.
abstract class Shape is
    field X: int
    field Y: int
    field color: string

    // A fresh object is initialized with values from the old object in
    // the constructor.
    method Shape(source: Shape) is
        if (source != null) then
            this.X = source.X
            this.Y = source.Y
            this.color = source.color

    // Clone operation always returns one of the Shape subclasses.
    abstract method clone(): Shape


// Concrete prototype. Cloning method creates a new object and passes itself to
// the constructor. Until constructor is finished, has a reference to a fresh
// clone. Therefore, nobody has access to a partly built clone. This helps to
// make the cloning result consistent.
class Rectangle extends Shape is
    field width: int
    field height: int

    method Rectangle(source: Rectangle) is
        // Parent constructor call is mandatory in order to copy private fields
        // defined in parent class.
        super(source)
        if (source != null) then
            this.width = source.width
            this.height = source.height

    method clone(): Shape is
        return new Rectangle(this)


class Circle extends Shape is
    field radius: int

    method Circle(source: Circle) is
        super(source)
        if (source != null) then
            this.radius = source.radius

    method clone(): Shape is
        return new Circle(this)


// Somewhere in client code.
class Application is
    field shapes: array of Shape

    method constructor() is
        Circle circle = new Circle()
        circle.X = 10
        circle.Y = 20
        circle.radius = 15
        shapes.add(circle)

        Circle anotherCircle = circle.clone()
        shapes.add(anotherCircle)
        // anotherCircle is the exact copy of circle.

        Rectangle rectangle = new Rectangle()
        rectangle.width = 10
        rectangle.height = 20
        shapes.add(rectangle)

    method businessLogic() is
        // Prototype rocks because it allows producing a copy of an object
        // without knowing anything about its type.
        Array shapesCopy = new Array of Shapes.

        // For instance, we do not know exact types of elements in shapes array.
        // All we know is that all of them are Shapes. But thanks to the
        // polymorphism, when we call the `clone` method on a shape, the program
        // checks its real class and runs the appropriate clone method, defined
        // in that class. That is why we get proper clones instead of a set of
        // simple Shape objects.
        foreach shapes as shape do
            shapesCopy.add(shape.clone())

        // The variable `shapesCopy` will contain exact copies of the `shape`
        // array's children.

適用性

  • 當你的代碼不應該依賴你要拷貝對象的具體類時。比如,對象的類是未知的,因爲你通過接口和他們協作。

    原型模式提供給客戶端一個和所有原型協作的接口。這個接口對所有支持克隆的對象是通用的。這使得客戶端對要克隆產品的具體類保持獨立。

  • 當你想要減少由不同方式配置相似對象(換句話說,每個類由唯一的字段值)組成的類層級結構的大小時。

    原型模式允許創建一個原型對象集合,這些對象代表了一個對象所有可能的配置。

    然後,客戶端代碼不是初始一個匹配一些配置的子類,而是尋找適當的原型並且克隆他。

如何實現

  1. 創建原型接口並在接口中聲明clone方法。你可以簡單的把這個方法添加到現有類層級中的所有類,如果你有類的話。

  2. 爲所有原型類添加一個可選的構造方法,這個構造方法接收當前類的一個對象。這個構造函數必須從傳遞的對象中將類中定義的所有字段的值拷貝到當前實例。然後,它應該調用父類構造方法去拷貝父類中的字段。

    如果你的編程語言不支持方法重載,你需要定義一個特別的方法來拷貝數據。構造方法僅僅是最便利的一個,因爲他在new操作後就開始拷貝。

  3. clone方法通常是有一行:運行一個使用原型構造方法的new操作符。注意,每個支持克隆的類必須明確重寫clone方法,克隆方法要new一個他自己的類。否則,克隆將會生產出一個父類的對象。

  4. 此步驟可選。創建一個原型註冊中心來存儲常用的原型。甚至是相同類的對象,只是配置方式不同。

    你可以在原型的基類中用工廠類或者工廠方法實現註冊中心。這個工廠方法可以根據客戶端代碼傳遞的參數查找適當的原型。搜索條件可以僅僅是一個字符串標籤或者複雜的搜索條件。在找到原型後,它應該克隆它並且把拷貝返回給客戶端。

    最後,我們應該把直接調用對象構造方法的clone方法改成調用原型註冊中心的工廠方法。

優點

  • 允許克隆對象而不耦合到具體的類。

  • 減少重複的實例代碼。

  • 更快的創建複雜對象。

  • 當你需要處理有許多可選配置的複雜對象時,可以把它當作替代子類的方案。

缺點

  • 引用了許多其他對象的複雜對象很難被克隆。

和其他模式的關係

  • 通常,設計從使用Factory Method開始(比較簡單,並且可以通過子類實現定製),逐漸演變到Abstract Factory,Prototype,或者Builder(更加複雜,但更靈活),因爲設計者發現它們需要更靈活的程序。

  • Abstract Factory類通常使用工廠方法實現,但也可以使用Prototype實現。

  • 當您需要將Command的副本保存到歷史記錄中時,Prototype可以提供幫助。

  • 大量使用Composite和Decorator模式的設計通常也可以從Prototype中受益。他允許克隆複雜結構,而不是從頭構建他們。

  • Prototype不需要子類,但是需要初始操作。Factory Method需要子類,但是不需要初始化這個步驟。

  • Prototype是更簡單的Memento替代,如果您想要把對象的某種狀態保存在歷史中,相當簡單,沒有外部資源的鏈接,或者鏈接很容易重新建立(Memento還不瞭解,此處很模糊)。

  • Abstract Factory,Builder和Prototype都可以實現爲Singleton。

總結

Prototype是創建型模式的一種,允許你克隆對象,即使被克隆對象很複雜,它不需要耦合特定的被拷貝對象的類。

用例:

java.lang.Object#clone() (class 應當實現java.lang.Cloneable 接口)

鑑定:通過clone()或者copy()等方法輕鬆識別原型模式。

參考

整理翻譯自:https://refactoring.guru/design-patterns/prototype

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章