原型模式(學習筆記)
1. 意圖
用原型例項指定建立物件的種類,並且通過拷貝這些原型物件建立新的物件。
2. 動機
如何複製一個物件?首先,建立一個屬於相同類的物件。然後,遍歷原始物件的所有成員變數,將成員變數複製到新物件中。
但這一過程存在兩個問題:1. 某些物件含有一些私有成員變數,它們在物件本身以外不可見。 2. 需要知道物件所屬的類才能建立複製品,所以程式碼必須依賴該類。即使可以接受額外的依賴性,還存在一個問題,有時只知道物件所實現的介面,而不知道其所屬的具體類,比如可向方法的某個引數傳入實現了某個介面的任何物件。
原型模式將克隆過程委派給被克隆的實際物件。 模式為所有支援克隆的物件聲明瞭一個通用介面, 該介面讓你能夠克隆物件, 同時又無需將程式碼和物件所屬類耦合。
3. 適用性
- 如果需要複製一些物件, 同時又希望程式碼獨立於這些物件所屬的具體類, 可以使用原型模式。
這一點考量通常出現在程式碼需要處理第三方程式碼通過介面傳遞過來的物件時。 即使不考慮程式碼耦合的情況, 你的程式碼也不能依賴這些物件所屬的具體類, 因為你不知道它們的具體資訊。
原型模式為客戶端程式碼提供一個通用介面, 客戶端程式碼可通過這一介面與所有實現了克隆的物件進行互動, 它也使得客戶端程式碼與其所克隆的物件具體類獨立開來。
- 如果子類的區別僅在於其物件的初始化方式, 那麼可以使用該模式來減少子類的數量。 別人建立這些子類的目的可能是為了建立特定型別的物件。
在原型模式中, 你可以使用一系列預生成的、 各種型別的物件作為原型。
客戶端不必根據需求對子類進行例項化, 只需找到合適的原型並對其進行克隆即可。
4. 結構
5. 效果
1)可以克隆物件, 而無需與它們所屬的具體類相耦合
2)可以克隆預生成原型, 避免反覆執行初始化程式碼
3)可以更方便地生成複雜物件
4)可以用繼承以外的方式來處理複雜物件的不同配置
5)克隆包含迴圈引用的複雜物件可能會非常麻煩
6. 程式碼實現
Shapes: 形狀列表
shapes/Shape.java: 通用形狀介面
package prototype.shapes;import java.util.Objects; /** * @author GaoMing * @date 2021/7/18 - 15:40 */ public abstract class Shape { public int x; public int y; public String color; public Shape(){ } public Shape(Shape target) { if (target != null) { this.x = target.x; this.y = target.y; this.color = target.color; } } public abstract Shape clone(); @Override public boolean equals(Object object2) { if (!(object2 instanceof Shape)) return false; Shape shape2 = (Shape) object2; return shape2.x == x && shape2.y == y && Objects.equals(shape2.color, color); } }
shapes/Circle.java: 簡單形狀
package prototype.shapes; /** * @author GaoMing * @date 2021/7/18 - 15:41 */ public class Circle extends Shape{ public int radius; public Circle(){ } public Circle(Circle target) { super(target); if (target != null) { this.radius = target.radius; } } @Override public Shape clone() { return new Circle(this); } @Override public boolean equals(Object object2) { if (!(object2 instanceof Circle) || !super.equals(object2)) return false; Circle shape2 = (Circle) object2; return shape2.radius == radius; } }
shapes/Rectangle.java: 另一個形狀
package prototype.shapes; /** * @author GaoMing * @date 2021/7/18 - 15:41 */ public class Rectangle extends Shape{ public int width; public int height; public Rectangle() { } public Rectangle(Rectangle target) { super(target); if (target != null) { this.width = target.width; this.height = target.height; } } @Override public Shape clone() { return new Rectangle(this); } @Override public boolean equals(Object object2) { if (!(object2 instanceof Rectangle) || !super.equals(object2)) return false; Rectangle shape2 = (Rectangle) object2; return shape2.width == width && shape2.height == height; } }
Demo.java: 克隆示例
package prototype; import prototype.shapes.Circle; import prototype.shapes.Shape; import prototype.shapes.Rectangle; import java.util.ArrayList; import java.util.List; /** * @author GaoMing * @date 2021/7/18 - 15:46 */ public class Demo { public static void main(String[] args){ List<Shape> shapes = new ArrayList<>(); List<Shape> shapesCopy = new ArrayList<>(); Circle circle = new Circle(); circle.x = 10; circle.y = 20; circle.radius = 15; circle.color = "red"; shapes.add(circle); Circle anotherCircle = (Circle) circle.clone(); shapes.add(anotherCircle); Rectangle rectangle = new Rectangle(); rectangle.width = 10; rectangle.height = 20; rectangle.color = "blue"; shapes.add(rectangle); cloneAndCompare(shapes, shapesCopy); } private static void cloneAndCompare(List<Shape> shapes, List<Shape> shapesCopy) { for (Shape shape : shapes) { shapesCopy.add(shape.clone()); } for (int i = 0; i < shapes.size(); i++) { if (shapes.get(i) != shapesCopy.get(i)) { System.out.println(i + ": Shapes are different objects (yay!)"); if (shapes.get(i).equals(shapesCopy.get(i))) { System.out.println(i + ": And they are identical (yay!)"); } else { System.out.println(i + ": But they are not identical (booo!)"); } } else { System.out.println(i + ": Shape objects are the same (booo!)"); } } } }
執行結果
0: Shapes are different objects (yay!) 0: And they are identical (yay!) 1: Shapes are different objects (yay!) 1: And they are identical (yay!) 2: Shapes are different objects (yay!) 2: And they are identical (yay!)
原型註冊站
可以實現中心化的原型註冊站(或工廠),其中包含一系列預定義的原型物件。這樣一來,就可以通過傳遞物件名稱或其他引數的方式從工廠處獲得新的物件。工廠將搜尋合適的原型,然後對其進行克隆複製,最後將副本返回。
Cache
cache/BundledShapeCache.java: 原型工廠
package prototype.cache; import prototype.shapes.Circle; import prototype.shapes.Rectangle; import prototype.shapes.Shape; import java.util.HashMap; import java.util.Map; /** * @author GaoMing * @date 2021/7/18 - 16:15 */ public class BundledShapeCache { private Map<String, Shape> cache = new HashMap<>(); public BundledShapeCache() { Circle circle = new Circle(); circle.x = 5; circle.y = 7; circle.radius = 45; circle.color = "Green"; Rectangle rectangle = new Rectangle(); rectangle.x = 6; rectangle.y = 9; rectangle.width = 8; rectangle.height = 10; rectangle.color = "Blue"; cache.put("Big green circle", circle); cache.put("Medium blue rectangle", rectangle); } public Shape put(String key, Shape shape) { cache.put(key, shape); return shape; } public Shape get(String key) { return cache.get(key).clone(); } }
Demo.java: 克隆示例
package prototype.cache; import prototype.shapes.Shape; /** * @author GaoMing * @date 2021/7/18 - 16:15 */ public class Demo { public static void main(String[] args) { BundledShapeCache cache = new BundledShapeCache(); Shape shape1 = cache.get("Big green circle"); Shape shape2 = cache.get("Medium blue rectangle"); Shape shape3 = cache.get("Medium blue rectangle"); if (shape1 != shape2 && !shape1.equals(shape2)) { System.out.println("Big green circle != Medium blue rectangle (yay!)"); } else { System.out.println("Big green circle == Medium blue rectangle (booo!)"); } if (shape2 != shape3) { System.out.println("Medium blue rectangles are two different objects (yay!)"); if (shape2.equals(shape3)) { System.out.println("And they are identical (yay!)"); } else { System.out.println("But they are not identical (booo!)"); } } else { System.out.println("Rectangle objects are the same (booo!)"); } } }
執行結果
Big green circle != Medium blue rectangle (yay!) Medium blue rectangles are two different objects (yay!) And they are identical (yay!)
7. 與其他模式的關係
- 原型可用於儲存命令模式的歷史記錄
- 可以通過原型模式來複制複雜結構(大量使用組合模式和裝飾模式的設計), 而非從零開始重新構造
- 原型並不基於繼承, 因此沒有繼承的缺點。 另一方面, 原型需要對被複制物件進行復雜的初始化。 工廠方法基於繼承, 但是它不需要初始化步驟
- 有時候原型可以作為備忘錄模式的一個簡化版本, 其條件是你需要在歷史記錄中儲存的物件的狀態比較簡單, 不需要連結其他外部資源, 或者連結可以方便地重建
8. 已知應用
Java 的 Cloneable (可克隆) 介面就是立即可用的原型模式。任何類都可通過實現該介面來實現可被克隆的性質。
java.lang.Object#clone()