传送门(参考资料):
1. GoF设计模式——C语言中文网
2. 廖雪峰学Java——设计模式
3. Java浅拷贝与深拷贝的实现方式
- 1. 原型模式简介
- 2. 原型模式的优势与缺点
- 3. 应用场景
- 4. 原型模式的结构
- 5. 原型模式的实现
- 6. 模型的扩展
- 7. 补充知识
- 7.1 Java的浅拷贝与深拷贝
- 7.2 原型模式会破坏单例模式吗?
- 原型模式:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象
- 生活中原型模式的例子:文件的复制与拷贝,3D打印
- 程序中原型模式的例子: Spring 中,原型模式应用的非常广泛,例如 scope=‘prototype’、JSON.parseObject() 等都是原型模式的具体应用
优势:
- Java自带的原型模式基于内存二进制流的复制,在性能上比直接 new 一个对象更加优良
- 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份,并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作
缺点:
- 需要配置 clone 方法
- clone 方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违背了开闭原则
- 当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆;需要清醒地实用深克隆与浅克隆
- 对象之间相同或相似,即只是个别的几个属性不同的时候
- 创建对象成本较大,例如初始化时间长,占用CPU太多,或者占用网络资源太多等,需要优化资源
- 创建一个对象需要繁琐的数据准备或访问权限等,需要提高性能或者提高安全性
- 系统中大量使用该类对象,且各个调用者都需要给它的属性重新赋值
- 主要包含以下角色:
1. 抽象原型类:规定了具体原型对象必须实现的接口 2. 具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象 3. 访问类:使用具体原型类中的 clone() 方法来复制新的对象
图片来自[1]
- 由于 Java 提供了对象的 clone() 方法,所以用 Java 实现原型模式相对简单
- 原型模式的克隆分为浅克隆和深克隆:
1. 浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址 2. 深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址
- Java 中的 Object 类提供了浅克隆的 clone() 方法,具体原型类只要实现 Cloneable接口就可实现对象的浅克隆重写clone方法,这里的 Cloneable 接口就是抽象原型类
- 浅克隆实现:
//具体原型类
class Realizetype implements Cloneable {
Realizetype() {
System.out.println("具体原型创建成功!");
}
public Object clone() throws CloneNotSupportedException {
System.out.println("具体原型复制成功!");
return (Realizetype) super.clone();
}
}
//原型模式的测试类
public class PrototypeTest {
public static void main(String[] args) throws CloneNotSupportedException {
Realizetype obj1 = new Realizetype();
Realizetype obj2 = (Realizetype) obj1.clone();
System.out.println("obj1==obj2?" + (obj1 == obj2));
}
}
6. 模型的扩展
- 原型模式可扩展为带原型管理器的原型模式,它在原型模式的基础上增加了一个原型管理器 PrototypeManager 类
- PrototypeManager 类用 HashMap 保存多个复制的原型,Client 类可以通过管理器的 get(String id) 方法从中获取复制的原型
图片来自[1]
- Java可以通过Object提供的clone方法便捷地实现浅拷贝,但实际上还有一种常用的浅拷贝方法,即以方法重载的方式,添加一个“拷贝构造方法”;该方法接收一个已有的本类示例,对示例进行拷贝;但这种方法实际上还是存在创建对象以及对属性赋值的操作
class Person{
private Age age;
private String name;
public Person(Age age,String name) {
this.age=age;
this.name=name;
}
//拷贝构造方法
public Person(Person p) {
this.name=p.name;
this.age=p.age;
}
public void setName(String name) {
this.name=name;
}
public String toString() {
return this.name+" "+this.age;
}
}
其实从这里我们也可以看出来,浅拷贝只对当前的对象进行了拷贝,对于对象所包含的引用型变量,其实指向的还是同一个
- Java实现深拷贝方式一:其实在调用clone方法时,会沿着对象的引用链走下去,如果我们保证了类中包含的引用对象都实现了Cloneable接口,并重写了clone方法,那么就可以完成深拷贝
- Java实现深拷贝方式二:一种更加快捷与方便地深拷贝方案是基于对象的序列化与反序列化操作来实现的,序列化操作会将所有允许序列化的属性都转换为字节流(也就是实现了深拷贝,因为我们有了完整的“内容”字节),这时候再通过反序列重建对象就可以将复制对象与原对象之间的引用指向完全隔离,实现深拷贝:
public class DeepCopyBySerialization {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Age a=new Age(xx);
Student stu1=new Student("xxx",x,xxx);
//通过序列化方法实现深拷贝
ByteArrayOutputStream bos=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(bos);
oos.writeObject(stu1);
oos.flush();
ObjectInputStream ois=new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
Student stu2=(Student)ois.readObject();
}
}
7.2 原型模式会破坏单例模式吗?
测试代码如下:
public class SingletonWithClone {
public static void main(String[] args) throws CloneNotSupportedException {
Teacher a = Teacher.getInstance();
Teacher b = (Teacher) a.clone();
b.id = 2;
System.out.println(a.equals(b));
a.teach();
b.teach();
}
}
class Teacher implements Cloneable {
public static int counter = 0;
public int id = 0;
private static volatile Teacher instance;
private Teacher(int id) {
this.id = id;
}
public static synchronized Teacher getInstance() {
if (instance == null) {
counter ++;
instance = new Teacher(counter);
}
return instance;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return (Teacher) super.clone();
}
public void teach() {
System.out.println("I am teaching now ! !" + this.id);
}
}
测试结果如下:
false I am teaching now ! !1 I am teaching now ! !2
这就说明了问题:如果单例模式混合了原型模式,那么单例就“不再成立”了。从上面的代码结果中我们可以看到,基于clone的拷贝方案,是基于对象的字节流的,也就是不去考量私有性等其他限制加载的性质,这样会使单例模式丧失其特有的性质。总地来说,单例模式与原型模式是关注于对象创建的不同的创建型模式
个人觉得,或许有保证单例模式不受原型模式干扰的方案,但暂时还没有想出来,如果各位读者有可能的解决方案,不防提出来大家一起讨论讨论



