1 原型模式的介绍
1.原型模式的定义
用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。
在这里,原型实例指定了要创建的对象的种类。
用这种方式创建对象非常高效,根本无须知道对象创建的细节。
2.为什么要用原型模式
在有些系统中,存在大量相同或相似对象的创建问题,如果用传统的构造函数来创建对象,会比较复杂且耗时耗资源,用原型模式生成对象就很高效。就像孙悟空拔下猴毛轻轻一吹就变出很多孙悟空一样简单。
3.原型模式的两种方式
分为: 浅拷贝、深拷贝
浅拷贝:将一个对象拷贝后,基本数据类型的变量(包括引用类型String类型)都会重新创建,而引用类型,指向的还是原对象所指向的。
深拷贝:将一个对象拷贝后,不论是基本数据类型还有引用类型,都是重新创建的。简单来说,就是深拷贝进行了完全彻底的拷贝,而浅拷贝不彻底。
2 原型模式的两种实现方式
克隆羊问题:
现在有一只白色的羊Tom,年龄为5岁,要求编写程序创建和Tom羊属性完全相同的10只羊。
1.传统方法
(1) 代码实现
public class Sheep{
private String name;
private int age;
public Sheep() {
}
public Sheep(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Sheep{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public static void main(String[] args) {
Sheep sheep = new Sheep("Tom",5);
Sheep sheep1 = new Sheep(sheep.getName(),sheep.getAge());
Sheep sheep2 = new Sheep(sheep.getName(),sheep.getAge());
Sheep sheep3 = new Sheep(sheep.getName(),sheep.getAge());
Sheep sheep4 = new Sheep(sheep.getName(),sheep.getAge());
System.out.println("sheep1 = " + sheep1+" "+"sheep1 = " + sheep1.hashCode());
System.out.println("sheep2 = " + sheep2+" "+"sheep2 = " + sheep2.hashCode());
System.out.println("sheep3 = " + sheep3+" "+"sheep3 = " + sheep3.hashCode());
System.out.println("sheep4 = " + sheep4+" "+"sheep4 = " + sheep4.hashCode());
}
sheep1 = Sheep{name='Tom', age=5} sheep1 = 1323165413
sheep2 = Sheep{name='Tom', age=5} sheep2 = 1560911714
sheep3 = Sheep{name='Tom', age=5} sheep3 = 939047783
sheep4 = Sheep{name='Tom', age=5} sheep4 = 1237514926
(2) 优缺点
1、优点是比较好理解,简单易操作。
2、在创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂时,效率较低
3、总是需要重新初始化对象,而不是动态地获得对象运行时的状态, 不够灵活
改进:
一个原型类,只需要实现Cloneable接口,覆写clone方法,此处clone方法可以改成任意的名称,因为Cloneable接口是个空接口,你可以任意定义实现类的方法名,如cloneA或者cloneB,因为此处的重点是super.clone()这句话,super.clone()调用的是Object的clone()方法,而在Object类中,clone()是native的
2.浅拷贝
(1) 代码实现
public class Sheep implements Cloneable{
private String name;
private int age;
public Sheep() {
}
public Sheep(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public Object clone(){
Sheep sheep = null;
try {
sheep = (Sheep)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return sheep;
}
@Override
public String toString() {
return "Sheep{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public static void main(String[] args) {
Sheep sheep = new Sheep("Tom",5);
Sheep sheep1 = (Sheep)sheep.clone();
Sheep sheep2 = (Sheep)sheep.clone();
Sheep sheep3 = (Sheep)sheep.clone();
Sheep sheep4 = (Sheep)sheep.clone();
System.out.println("sheep1 = " + sheep1+" "+"sheep1 = " + sheep1.hashCode());
System.out.println("sheep2 = " + sheep2+" "+"sheep2 = " + sheep2.hashCode());
System.out.println("sheep3 = " + sheep3+" "+"sheep3 = " + sheep3.hashCode());
System.out.println("sheep4 = " + sheep4+" "+"sheep4 = " + sheep4.hashCode());
}
sheep1 = Sheep{name='Tom', age=5} sheep1 = 1323165413
sheep2 = Sheep{name='Tom', age=5} sheep2 = 1560911714
sheep3 = Sheep{name='Tom', age=5} sheep3 = 939047783
sheep4 = Sheep{name='Tom', age=5} sheep4 = 1237514926
(2) 优缺点
1、对于数据类型是基本数据类型的成员变量(包括引用类型String类型),浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。
2、对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。
3、前面我们克隆羊就是浅拷贝。
4、浅拷贝是使用默认的 clone()方法来实现。
sheep = (Sheep) super.clone();
5、如果成员变量有引用类型(除过引用类型String类型),则浅拷贝会进行的是引用值,存在的问题就是,当拷贝后的某一个对象的引用成员变量发生变化时,其他的所有的对象的引用类型值都会发生变化。
改进:
如果成员变量有引用类型(除过引用类型String类型),则使用深拷贝。
2.深拷贝
(1) 代码实现
import java.io.Serializable;
public class Friend implements Serializable, Cloneable {
private String f_name;
private int F_age;
public Friend() {
}
public Friend(String f_name, int f_age) {
this.f_name = f_name;
F_age = f_age;
}
public String getF_name() {
return f_name;
}
public void setF_name(String f_name) {
this.f_name = f_name;
}
public int getF_age() {
return F_age;
}
public void setF_age(int f_age) {
F_age = f_age;
}
@Override
public Object clone() {
Friend friend = null;
try {
friend = (Friend)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return friend;
}
@Override
public String toString() {
return "Friend{" +
"f_name='" + f_name + '\'' +
", F_age=" + F_age +
'}';
}
}
import java.io.Serializable;
public class Sheep implements Serializable, Cloneable{
private String name;
private int age;
private Friend friend;
public Sheep() {
}
public Sheep(String name, int age, Friend friend) {
this.name = name;
this.age = age;
this.friend = friend;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Friend getFriend() {
return friend;
}
public void setFriend(Friend friend) {
this.friend = friend;
}
@Override
public Object clone(){
Sheep sheep = null;
try {
sheep = (Sheep)super.clone();
sheep.setFriend((Friend)friend.clone());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return sheep;
}
public Object deepClone() {
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
try {
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this);
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
Sheep copySheep = (Sheep)ois.readObject();
return copySheep;
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
try {
bos.close();
oos.close();
bis.close();
ois.close();
} catch (Exception e2) {
System.out.println(e2.getMessage());
}
}
}
@Override
public String toString() {
return "Sheep{" +
"name='" + name + '\'' +
", age=" + age +
", friend=" + friend +
'}';
}
}
public static void main(String[] args) {
Sheep sheep = new Sheep("Tom",5);
Sheep sheep1 = (Sheep)sheep.clone();
Sheep sheep2 = (Sheep)sheep.clone();
Sheep sheep3 = (Sheep)sheep.clone();
Sheep sheep4 = (Sheep)sheep.clone();
System.out.println("sheep1 = " + sheep1+" "+"sheep1 = " + sheep1.hashCode());
System.out.println("sheep2 = " + sheep2+" "+"sheep2 = " + sheep2.hashCode());
System.out.println("sheep3 = " + sheep3+" "+"sheep3 = " + sheep3.hashCode());
System.out.println("sheep4 = " + sheep4+" "+"sheep4 = " + sheep4.hashCode());
}
sheep1 = Sheep{name='Tom', age=5, friend=Friend{f_name='Jow', F_age=11}} sheep1 = 1586270964
sheep2 = Sheep{name='Tom', age=5, friend=Friend{f_name='Jow', F_age=11}} sheep2 = 110718392
sheep3 = Sheep{name='Tom', age=5, friend=Friend{f_name='Jow', F_age=11}} sheep3 = 425918570
sheep4 = Sheep{name='Tom', age=5, friend=Friend{f_name='Jow', F_age=11}} sheep4 = 2143192188
(2) 总结
温馨提示:同上面代码一样,可跳过
//序列化方式一
@Override
public Object clone(){
Sheep sheep = null
//这里完成对基本数据类型(属性)和String的克隆
try {
sheep = (Sheep)super.clone()
//对引用类型的属性,进行单独处理
sheep.setFriend((Friend)friend.clone())
} catch (CloneNotSupportedException e) {
e.printStackTrace()
}
return sheep
}
//方式二
public Object deepClone() {
//创建流对象
ByteArrayOutputStream bos = null
ObjectOutputStream oos = null
ByteArrayInputStream bis = null
ObjectInputStream ois = null
try {
//序列化
bos = new ByteArrayOutputStream()
oos = new ObjectOutputStream(bos)
oos.writeObject(this)
//反序列化
bis = new ByteArrayInputStream(bos.toByteArray())
ois = new ObjectInputStream(bis)
Sheep copySheep = (Sheep)ois.readObject()
return copySheep
} catch (Exception e) {
e.printStackTrace()
return null
} finally {
//关闭流
try {
bos.close()
oos.close()
bis.close()
ois.close()
} catch (Exception e2) {
System.out.println(e2.getMessage())
}
}
}
3 原型模式的注意事项和细节
1、创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率。
2、不用重新初始化对象,而是动态地获得对象运行时的状态。
3、如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需修改代码。
4、在实现深克隆的时候可能需要比较复杂的代码。
5、缺点:需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改 其源代码,违背了 ocp 原则,这点需要注意.
6、值得注意的是,使用clone方法创建的新对象的构造函数是不会被执行的,也就是说会绕过任何构造函数(有参和无参),因为clone方法的原理是从堆内存中以二进制流的方式进行拷贝,直接分配一块新内存。
4 应用
待补充:应用、serializable关键字