设计模式-原型模式
原型模式
什么是原型模式?
原型模式是一种创建型设计模式,它允许你复制现有的对象,而无需使你的代码依赖于它们的类。
简单来说,就是你有一个已经创建好的对象(原型),当你需要一个新的、与原型相似的对象时,你不必通过 new 关键字和构造函数去一步步创建,而是直接"克隆"或"复制"这个原型对象,然后可以对克隆出来的对象进行一些修改以满足特定需求。
核心思想:
通过克隆一个现有的实例来创建新的实例,而不是通过实例化类来创建。这个“原型”实例本身就能够创建自己的副本。
类比:
-
复印机: 你有一份原稿(原型),复印机(克隆机制)可以制作出多份副本。每份副本都可以独立修改,比如在上面写字。
-
细胞分裂: 一个细胞(原型)可以分裂成两个或多个相同的细胞(克隆体)。
-
软件中的 "Ctrl+C" (复制) 和 "Ctrl+V" (粘贴): 你复制一个对象,然后粘贴它,得到一个新的、与原对象内容相同的对象。
原型模式的参与者:
-
Prototype (原型接口/抽象类):
-
声明一个克隆自身的接口。在 Java 中,这通常是通过实现 java.lang.Cloneable 接口并重写 Object 类的 clone() 方法来实现的。
-
也可以是自定义的接口,比如 Prototype cloneMe();
-
-
ConcretePrototype (具体原型类):
-
实现原型接口中声明的克隆操作。
-
它负责复制自身的数据。
-
-
Client (客户端):
-
让一个原型对象克隆自身来创建一个新的对象。
-
客户端不需要知道具体原型类的名称,只需要通过原型接口来操作。
-
图示 (UML):
classDiagramclass Prototype {<<Interface>>+clone(): Prototype}class ConcretePrototypeA {-fieldA: String+ConcretePrototypeA(fieldA: String)+setFieldA(fieldA: String)+clone(): Prototype}class ConcretePrototypeB {-fieldB: int+ConcretePrototypeB(fieldB: int)+setFieldB(fieldB: int)+clone(): Prototype}Prototype <|.. ConcretePrototypeAPrototype <|.. ConcretePrototypeB class Client {+createAndUsePrototype(prototype: Prototype)}Client --> Prototype : uses
为什么使用原型模式?(优点)
-
性能提升: 如果对象的创建过程非常复杂或耗时(例如,需要从数据库加载大量数据,或进行复杂的计算),通过克隆现有对象可以显著提高性能。
-
简化对象创建: 避免了使用 new 关键字和复杂的构造函数链。特别是当有多个构造函数,或者构造函数参数很多时,克隆一个配置好的原型会更方便。
-
运行时动态添加/删除产品: 可以通过注册不同原型对象,在运行时动态地改变要创建的对象类型。
-
解耦: 客户端代码与具体的产品类解耦。客户端只需要知道原型接口。
-
方便创建复杂对象: 当一个对象包含许多部分,并且这些部分也需要被复制时,原型模式可以提供一种简洁的方式来创建这些复杂对象的副本。
关键点:浅拷贝 (Shallow Copy) vs 深拷贝 (Deep Copy)
这是原型模式中非常重要的一个概念:
-
浅拷贝 (Shallow Copy):
-
当克隆一个对象时,只复制对象本身和其中包含的值类型的成员变量。
-
对于引用类型的成员变量,只复制引用(内存地址),而不复制引用所指向的对象。
-
因此,原对象和克隆对象的引用类型成员变量会指向同一个内存中的对象。修改其中一个克隆对象的引用类型成员,会影响到原对象(或其他克隆对象)的这个成员。
-
Java 中 Object 类的默认 clone() 方法执行的是浅拷贝。
-
-
深拷贝 (Deep Copy):
-
当克隆一个对象时,不仅复制对象本身和值类型的成员,还会递归地复制所有引用类型的成员变量所指向的对象。
-
因此,原对象和克隆对象的引用类型成员变量会指向不同的、内容相同的对象。它们是完全独立的。
-
实现深拷贝通常需要自己重写 clone() 方法,并对引用类型的成员进行显式的克隆操作。
-
选择浅拷贝还是深拷贝取决于具体需求。如果希望克隆对象和原对象共享某些内部状态(不推荐,容易出错),可以使用浅拷贝。大多数情况下,为了保证克隆对象的独立性,需要实现深拷贝。
Java 代码示例:
我们以一个简单的图形类 Shape 作为例子。
1. Prototype 接口 (使用 Cloneable 和 clone() 方法) 在 Java 中,我们通常让原型类实现 java.lang.Cloneable 标记接口,并重写 Object 类的 clone() 方法。
// Shape.java (抽象原型类) import java.util.Objects; public abstract class Shape implements Cloneable { // 1. 实现 Cloneable 接口private String id;protected String type;// 假设有一个复杂的配置对象,我们想在深拷贝时也复制它private ComplexConfig config; public Shape(String id) {this.id = id;this.config = new ComplexConfig("default_param"); // 初始化复杂对象} public abstract void draw(); public String getId() {return id;} public void setId(String id) {this.id = id;} public String getType() {return type;} public ComplexConfig getConfig() {return config;} public void setConfigParam(String param) {this.config.setParam(param);} // 2. 重写 clone 方法@Overridepublic Object clone() {Object clone = null;try {// 默认的 super.clone() 是浅拷贝clone = super.clone(); // 为了实现深拷贝,需要对引用类型的成员进行显式克隆// 对于 Shape 类的 config 成员Shape clonedShape = (Shape) clone;clonedShape.config = (ComplexConfig) this.config.clone(); //假设 ComplexConfig 也实现了 Cloneable } catch (CloneNotSupportedException e) {e.printStackTrace(); // Cloneable 已实现,理论上不应发生}return clone;} @Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Shape shape = (Shape) o;return Objects.equals(id, shape.id) && Objects.equals(type, shape.type) && Objects.equals(config, shape.config);} @Overridepublic int hashCode() {return Objects.hash(id, type, config);} } // ComplexConfig.java (一个需要被深拷贝的引用类型成员) class ComplexConfig implements Cloneable {private String param; public ComplexConfig(String param) {this.param = param;} public String getParam() {return param;} public void setParam(String param) {this.param = param;} @Overridepublic Object clone() {try {return super.clone(); // ComplexConfig 只有String,浅拷贝即可,因为String是不可变的} catch (CloneNotSupportedException e) {e.printStackTrace();return null;}} @Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;ComplexConfig that = (ComplexConfig) o;return Objects.equals(param, that.param);} @Overridepublic int hashCode() {return Objects.hash(param);} @Overridepublic String toString() {return "ComplexConfig{param='" + param + "'}";} }
2. ConcretePrototype (具体原型类)
// Rectangle.java public class Rectangle extends Shape {private int width;private int height; public Rectangle(String id, int width, int height) {super(id);this.type = "Rectangle";this.width = width;this.height = height;} public int getWidth() {return width;} public int getHeight() {return height;} @Overridepublic void draw() {System.out.println("Inside Rectangle::draw() method. ID: " + getId() + ", Type: " + getType() +", Width: " + width + ", Height: " + height + ", Config: " + getConfig());} // Rectangle 特有的 clone 逻辑 (如果需要比 Shape 更深的拷贝或特定处理)// 如果 Shape 的 clone 已经处理了所有共享的父类成员,这里可能不需要重写// 但如果 Rectangle 有自己的引用类型成员需要深拷贝,则需要重写@Overridepublic Object clone() {Rectangle cloned = (Rectangle) super.clone(); // 先调用父类的克隆 (已经处理了config的深拷贝)// 如果 Rectangle 有自己的引用类型成员,在这里进行深拷贝// cloned.rectangleSpecificRefType = (RectangleSpecificRefType) this.rectangleSpecificRefType.clone();return cloned;} } // Circle.java public class Circle extends Shape {private int radius; public Circle(String id, int radius) {super(id);this.type = "Circle";this.radius = radius;} public int getRadius() {return radius;} @Overridepublic void draw() {System.out.println("Inside Circle::draw() method. ID: " + getId() + ", Type: " + getType() +", Radius: " + radius + ", Config: " + getConfig());} // 类似 Rectangle,如果 Circle 有自己的引用类型成员需要深拷贝,则需要重写 clone@Overridepublic Object clone() {return super.clone(); // 对于这个简单例子,父类的clone已足够} }
3. Client (客户端)
// PrototypeDemo.java import java.util.HashMap; import java.util.Map; // 可以有一个原型管理器来存储和获取原型 class ShapeCache {private static Map<String, Shape> shapeMap = new HashMap<>(); public static Shape getShape(String shapeId) {Shape cachedShape = shapeMap.get(shapeId);if (cachedShape != null) {// 从缓存中获取原型对象,并克隆它return (Shape) cachedShape.clone();}return null;} // 加载初始原型到缓存public static void loadCache() {Circle circle = new Circle("circle1", 10);circle.setConfigParam("CircleSpecificConfig");shapeMap.put(circle.getId(), circle); Rectangle rectangle = new Rectangle("rect1", 20, 30);rectangle.setConfigParam("RectangleDefaultConfig");shapeMap.put(rectangle.getId(), rectangle);} } public class PrototypeDemo {public static void main(String[] args) {ShapeCache.loadCache(); // 获取克隆对象Shape clonedShape1 = ShapeCache.getShape("circle1");Shape clonedShape2 = ShapeCache.getShape("rect1");Shape clonedShape3 = ShapeCache.getShape("circle1"); // 再次克隆 circle1 System.out.println("--- Original Prototypes (conceptual, as they are in cache) ---");// 我们不能直接从cache里拿出来用,因为那不是克隆体// Shape originalCircle = ShapeCache.shapeMap.get("circle1"); // 假设可以访问// originalCircle.draw(); System.out.println("\n--- Cloned Shapes ---");clonedShape1.draw();clonedShape2.draw();clonedShape3.draw(); System.out.println("\n--- Modifying Cloned Shape 1 (Circle) ---");// 修改克隆对象的属性((Circle) clonedShape1).setId("clonedCircleNewId"); // 修改基本类型clonedShape1.setConfigParam("ClonedCircle1_NewConfig"); // 修改引用类型内部数据clonedShape1.draw(); System.out.println("\n--- Checking Cloned Shape 3 (another Circle clone) to see if it's affected ---");// 验证 clonedShape3 是否受到 clonedShape1 修改的影响clonedShape3.draw(); // 由于是深拷贝 config,clonedShape3 的 config 不应该被修改 System.out.println("\n--- Verifying object references (should be different) ---");System.out.println("clonedShape1 == clonedShape3: " + (clonedShape1 == clonedShape3)); // false, 不同对象System.out.println("clonedShape1.getConfig() == clonedShape3.getConfig(): " +(clonedShape1.getConfig() == clonedShape3.getConfig())); // false, config 对象也不同 (深拷贝) // 如果是浅拷贝,config会相同// 如果Shape的clone方法中没有这句:clonedShape.config = (ComplexConfig) this.config.clone();// 那么clonedShape1.getConfig() == clonedShape3.getConfig() 会是 true System.out.println("\n--- Comparing original prototype (from cache) config with clonedShape3 config ---");Shape originalCircleFromCache = ShapeCache.getShape("circle1"); // 获取一个新的克隆体,代表原始状态System.out.println("Original Circle Config (from a fresh clone): " + originalCircleFromCache.getConfig());System.out.println("Cloned Shape 3 Config: " + clonedShape3.getConfig());// 它们应该是内容相同但引用不同的对象System.out.println("originalCircleFromCache.getConfig() == clonedShape3.getConfig(): " +(originalCircleFromCache.getConfig() == clonedShape3.getConfig())); // falseSystem.out.println("originalCircleFromCache.getConfig().equals(clonedShape3.getConfig()): " +(originalCircleFromCache.getConfig().equals(clonedShape3.getConfig()))); // true (if equals is properly implemented in ComplexConfig) } }
何时使用原型模式?
-
当对象的创建成本很高(例如,初始化时间长,或者需要访问外部资源)。
-
当需要创建的对象与现有对象只有微小差异时。
-
当一个系统应该独立于其产品的创建、构成和表示时。
-
当要实例化的类是在运行时指定时,例如,通过动态加载。
-
为了避免构建一个与产品类层次平行的工厂类层次时(例如,在抽象工厂模式中,如果产品种类很多,工厂也会很多)。
缺点:
-
每个具体原型类都必须实现 clone() 方法,这可能会有些复杂,特别是当需要实现深拷贝且对象结构复杂时。
-
克隆具有循环引用的对象可能会非常棘手。
总而言之,原型模式通过复制现有对象来创建新对象,提供了一种高效且灵活的对象创建方式,尤其适用于创建成本高或需要动态指定创建类型的场景。正确处理浅拷贝和深拷贝是使用此模式的关键。