# Prototype Pattern
# 介绍
原型模式 (Prototype Pattern) 是一种创建型设计模式。它通过克隆现有的对象来创建新对象。这种方式的主要优势是在运行时不需要知道具体的类,只需要知道一个实例对象即可。在 Java 中,我们可以使用 java.lang.Cloneable 接口和 clone () 方法来实现原型模式
意图:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
主要解决:在运行期建立和删除原型。
** 如何解决:** 利用已有的一个原型对象,快速地生成和原型对象一样的实例。
关键代码: 1、实现克隆操作,在 JAVA 实现 Cloneable 接口,重写 clone (),在 .NET 中可以使用 Object 类的 MemberwiseClone () 方法来实现对象的浅拷贝或通过序列化的方式来实现深拷贝。 2、原型模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些 "易变类" 拥有稳定的接口。
应用实例: 1、细胞分裂。 2、JAVA 中的 Object clone () 方法。
优点: 1、性能提高。 2、逃避构造函数的约束。
缺点: 1、配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。 2、必须实现 Cloneable 接口。
使用场景: 1、资源优化场景。 2、类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。 3、性能和安全要求的场景。 4、通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。 5、一个对象多个修改者的场景。 6、一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。 7、在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与 Java 融为浑然一体,大家可以随手拿来使用。
** 注意事项:** 与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。浅拷贝实现 Cloneable,重写,深拷贝是通过实现 Serializable 读取二进制流。
我们将创建一个抽象类 Shape 和扩展了 Shape 类的实体类。下一步是定义类 ShapeCache,该类把 shape 对象存储在一个 Hashtable 中,并在请求的时候返回它们的克隆。
PrototypePatternDemo 类使用 ShapeCache 类来获取 Shape 对象。
以下是一个使用原型模式的 java 代码示例:
// 抽象原型类 | |
public abstract class Shape implements Cloneable { | |
private String id; | |
protected String type; | |
abstract void draw(); | |
public String getType() { | |
return type; | |
} | |
public String getId() { | |
return id; | |
} | |
public void setId(String id) { | |
this.id = id; | |
} | |
public Object clone() { | |
Object clone = null; | |
try { | |
clone = super.clone(); | |
} catch (CloneNotSupportedException e) { | |
e.printStackTrace(); | |
} | |
return clone; | |
} | |
} | |
// 具体原型类 | |
public class Rectangle extends Shape { | |
public Rectangle() { | |
type = "Rectangle"; | |
} | |
@Override | |
public void draw() { | |
System.out.println("Inside Rectangle::draw() method."); | |
} | |
} | |
public class Square extends Shape { | |
public Square() { | |
type = "Square"; | |
} | |
@Override | |
public void draw() { | |
System.out.println("Inside Square::draw() method."); | |
} | |
} | |
public class Circle extends Shape { | |
public Circle() { | |
type = "Circle"; | |
} | |
@Override | |
public void draw() { | |
System.out.println("Inside Circle::draw() method."); | |
} | |
} | |
// 创建一个类,从数据库获取实体类,并把它们存储在一个 Hashtable 中。 | |
public class ShapeCache { | |
private static Hashtable<String, Shape> shapeMap = new Hashtable<String, Shape>(); | |
public static Shape getShape(String shapeId) { | |
Shape cachedShape = shapeMap.get(shapeId); | |
return (Shape) cachedShape.clone(); | |
} | |
// 对每种形状都运行数据库查询,并创建该形状 | |
// shapeMap.put(shapeKey, shape); | |
// 例如,我们要添加三种形状 | |
public static void loadCache() { | |
Circle circle = new Circle(); | |
circle.setId("1"); | |
shapeMap.put(circle.getId(), circle); | |
Square square = new Square(); | |
square.setId("2"); | |
shapeMap.put(square.getId(), square); | |
Rectangle rectangle = new Rectangle(); | |
rectangle.setId("3"); | |
shapeMap.put(rectangle.getId(), rectangle); | |
} | |
} | |
//PrototypePatternDemo 使用 ShapeCache 类来获取存储在 Hashtable 中的形状的克隆。 | |
// 使用原型模式的测试类 | |
public class PrototypeTest { | |
public static void main(String[] args) throws CloneNotSupportedException { | |
ShapeCache.loadCache(); | |
Shape clonedShape = (Shape) ShapeCache.getShape("1"); | |
System.out.println("Shape : " + clonedShape.getType()); | |
Shape clonedShape2 = (Shape) ShapeCache.getShape("2"); | |
System.out.println("Shape : " + clonedShape2.getType()); | |
Shape clonedShape3 = (Shape) ShapeCache.getShape("3"); | |
System.out.println("Shape : " + clonedShape3.getType()); | |
} | |
} |
在这个例子中, Shape
是抽象原型类,它定义了 clone()
方法; Rectangle
、 Square
和 Circle
是具体原型类,它们实现了 Shape
类的 draw()
方法; PrototypeTest
是测试类,它使用 ShapeCache
类来获取 Shape
对象的克隆
# 使用 Serializable 实现深拷贝
import java.io.*; | |
class MyClass implements Serializable { | |
private int num; | |
private String str; | |
public MyClass(int num, String str) { | |
this.num = num; | |
this.str = str; | |
} | |
public MyClass deepCopy() { | |
try { | |
// 将对象写入到字节输出流中 | |
ByteArrayOutputStream bos = new ByteArrayOutputStream(); | |
ObjectOutputStream oos = new ObjectOutputStream(bos); | |
oos.writeObject(this); | |
// 从字节输入流中读取对象 | |
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); | |
ObjectInputStream ois = new ObjectInputStream(bis); | |
return (MyClass) ois.readObject(); | |
} catch (IOException | ClassNotFoundException e) { | |
e.printStackTrace(); | |
return null; | |
} | |
} | |
} |
这段代码中,我们首先创建了一个实现了 ** Serializable
接口的类 MyClass
。然后在 MyClass
中定义了一个 deepCopy
方法,该方法首先使用 ObjectOutputStream
将当前对象写入到一个 ByteArrayOutputStream
中,然后使用 ObjectInputStream
从一个 ByteArrayInputStream
** 中读取对象,从而实现深拷贝。
需要注意的是,这种方法只适用于所有成员变量都是可序列化的情况。如果有不可序列化的成员变量,那么在序列化过程中会抛出 ** NotSerializableException
** 异常。此外,这种方法可能会有一些性能问题,因为它涉及到序列化和反序列化操作,这些操作相对较慢并且可能会消耗较多的内存。所以在实际使用时需要根据具体情况来选择是否使用这种方法。