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()