Prototype 原型模式:(实际也叫克隆模式)用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里,原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效,根本无须知道对象创建的细节
原型模式的优点:
- Java 自带的原型模式基于内存二进制流的复制,在性能上比直接 new 一个对象更加优良
- 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份,并将其状态保存起来,简化了创建对象的过程,以便 - 在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作。
原型模式的缺点: - 需要为每一个类都配置一个 clone 方法
- clone 方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违背了开闭原则。
- 当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类 - 都必须支持深克隆,实现起来会比较麻烦。因此,深克隆、浅克隆需要运用得当。
结构图:
实例:创建一个Person对象, 通过原型模式克隆一份(实际就是调用Object的clone方法)
实现需要两个步骤:
1、实现clonable接口,这个接口虽然没有任何方法,只是一个标记,但是必须实现它,否则运行出错(编译不会出错)
2、调用Object父类的clone方法(看源码可以看出,clone方法是native方法,实际底层用的C++实现的。就是直接从内存copy一份)
public class Prototype{
public static void main(String[] args) throws CloneNotSupportedException {
Person p = new Person("张三", 12, new Bird("小鸟", 2));
Person p2 = (Person)p.clone();
System.out.println(p.name + "--------" + p2.name);
System.out.println(p.age + "--------" + p2.age);
System.out.println(p.bird.name +"-------"+p2.bird.name);
System.out.println(p.bird.age +"-------"+p2.bird.age);
System.out.println("分割线--------------------------------");
//改变p对象属性值--看看p2的值会不会变
p.name = "李四";
p.age = 10;
p.bird.name="大鸟";
p.bird.age = 3;
System.out.println(p.name + "--------" + p2.name);
System.out.println(p.age + "--------" + p2.age);
System.out.println(p.bird.name +"-------"+p2.bird.name);
System.out.println(p.bird.age +"-------"+p2.bird.age);
}
}
class Person implements Cloneable{ //必须实现Cloneable这个接口,只是一个标记,没有任何用处,但是必须有
String name;
int age;
Bird bird;
public Person(String name, int age, Bird bird) {
this.name = name;
this.age = age;
this.bird = bird;
}
@Override
public Object clone() throws CloneNotSupportedException { //需要被克隆的对象实现一下这个方法,调用父类clone()方法即可
return super.clone();
}
}
class Bird{
String name;
int age;
public Bird(String name, int age) {
this.name = name;
this.age = age;
}
}
打印值:
// 可以看出克隆出来后,两个对象一模一样
张三--------张三
12--------12
小鸟-------小鸟
2-------2
分割线--------------------------------
// 改变p属性的值后,看看两个对象的值得变化
李四--------张三
10--------12
大鸟-------大鸟 // 这里我只是改变了 p 对象的值,为什么 p2 的值也跟着变了?
3-------3
通过上面可以看出一个问题,基本变量通过clone方法可以完全克隆一份,互相没有牵扯但是引用对象的值还是一模一样的(这是因为Bird只是个引用,克隆一份,Bird对象还是同一个)
看图:
这里就牵扯出一个概念:浅克隆,深克隆
浅克隆
只会克隆基本类型,对引用类型是克隆不了的。
(有些人可能会问String不是基本类型,String也是个对象类型,他也是一个引用。String比较特殊,虽然是个引用类型,但是他指向一个常量池。当你改变String的引用的时候,另一个引用不会变。看下图)
深克隆
无论基本类型还是引用类型都需要克隆一份
注: 如果要完成深克隆就需要把需要克隆的引用对象也按照Person类的形式做一遍,实现Clonable接口,重写clone方法。
深刻了实例:
public class Prototype{
public static void main(String[] args) throws CloneNotSupportedException {
Person p = new Person("张三", 12, new Bird("小鸟", 2));
Person p2 = (Person)p.clone();
System.out.println(p.name + "--------" + p2.name);
System.out.println(p.age + "--------" + p2.age);
System.out.println(p.bird.name +"-------"+p2.bird.name);
System.out.println(p.bird.age +"-------"+p2.bird.age);
System.out.println("分割线--------------------------------");
//改变属性值
p.name = new String("李四");
p.age = 10;
p.bird.name="大鸟";
p.bird.age = 3;
System.out.println(p.name + "--------" + p2.name);
System.out.println(p.age + "--------" + p2.age);
System.out.println(p.bird.name +"-------"+p2.bird.name);
System.out.println(p.bird.age +"-------"+p2.bird.age);
}
}
class Person implements Cloneable{
String name;
int age;
Bird bird;
public Person(String name, int age, Bird bird) {
this.name = name;
this.age = age;
this.bird = bird;
}
@Override
public Object clone() throws CloneNotSupportedException { //这里也需要做一些更改
Person p = (Person) super.clone();
p.bird = (Bird)bird.clone();
return p;
}
}
class Bird implements Cloneable{ //这里也实现Cloneable接口
String name;
int age;
public Bird(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
运行结果:
张三--------张三
12--------12
小鸟-------小鸟
2-------2
分割线-------------------------------- // 看下面,p对象的更改,完全影响不到p2对象了。实现了深克隆
李四--------张三
10--------12
大鸟-------小鸟
3-------2
总结: 从上面可以看出,如果克隆带有引用的对象,写起来是比较麻烦的。特别是属性引用多的,每个引用对象都要实现一遍Cloneable接口,重写一下clone方法,主类的clone方法也要手动赋值,挺麻烦的。浅克隆还是比较好用的。