原型模式就是用已存在的对象实例,通过复制该实例生成一个相同或类似的对象。而且使用原型模式非常高效简便。
实现思路:在java下,所有的类的超类Object有提供对象的clone()方法,所以使用java实现这种原型模式还是比较简单的。涉及到的模型角色:
- 1.抽象原型:由接口实现,继承至Cloneable接口,给出所有具体原型需要的接口,至少有一个clone 的方法;
- 2.具体原型:继承抽象原型接口,实现其方法,返回被复制的对象;
- 3.使用者:发起复制的类;
以一个学生类作为栗子:
抽象原型:
public interface StudentPro extends Cloneable{
StudentPro clone();
}
具体原型:
public class Student implements StudentPro {
public String name;
public int age;
public int sex;
public String address;
@Override
public StudentPro clone() {
try {
return (StudentPro) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
@Override
public String toString() {
return "[" + name + ", " + age + ", " + sex + ", " + address + "]";
}
}
使用:
Student s1 = new Student();
s1.address = "广东天河";
s1.name = "哈哈";
s1.sex = 1;
s1.age = 2;
Log.d("TAG", "s1: " + s1.toString());
Student s2 = (Student) s1.clone();
Log.d("TAG", "s2: " + s2.toString());
结果:
D/TAG: s1: [哈哈, 2, 1, 广东天河]
D/TAG: s2: [哈哈, 2, 1, 广东天河]
可以看到俩个对象属性的内容都是一样的。
原型模式的复制也是有分类的。根据复制的深度可分为深克隆跟浅克隆。浅克隆是克隆对象的基本类型的属性值跟引用类型的地址值。意思就是引用类型的对象使用的是同一个对象来的,并没有产生一个新的对象。而深克隆则是不管是基本类型还是引用类型统统都克隆,产生新的对象。修改前面的例子,验证一下:
新增一个类对象(同时修改toString方法):
public class Country {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
在具体模型新增2个成员变量:
private List likes;
private Country country;
public List getLikes() {
return likes;
}
public void setLikes(List likes) {
this.likes = likes;
}
public Country getCountry() {
return country;
}
public void setCountry(Country country) {
this.country = country;
}
然后使用修改:
Student s1 = new Student();
s1.address = "广东";
s1.name = "哈哈";
s1.sex = 1;
s1.age = 2;
List<String> list = new ArrayList<>();
list.add("rap");
list.add("跳");
s1.setLikes(list);
Country country = new Country();
country.setName("XXC");
s1.setCountry(country);
Log.d("TAG", "s1: " + s1.toString()); //一开始的s1
Student s2 = (Student) s1.clone();
s2.getLikes().add("打篮球");
s2.getCountry().setName("OOP");
Log.d("TAG", "s2: " + s2.toString()); //s2
Log.d("TAG", "s1: " + s1.toString()); //第二次 s1
结果:
D/TAG: s1: [哈哈, 2, 1, 广东,XXC, [rap, 跳]]
D/TAG: s2: [哈哈, 2, 1, 广东,OOP, [rap, 跳, 打篮球]]
D/TAG: s1: [哈哈, 2, 1, 广东,OOP, [rap, 跳, 打篮球]]
看到结果,在对s2的修改同时也对s1生效了。所以 getLikes(),getCountry() 里拿到的是一个对象的引用,而不是一个新的对象。拿例子来说,想要克隆s2同时也克隆出一个新的likes,country对象就要用到深克隆了。
深克隆是利用序列化实现,实现Serializable接口。是所有的都要实现Serializable接口,比如引用里面的引用对象也要实现该接口,当然没有的话异常也是会提醒你的。
在抽象原型上修改,实现Serializable 接口,并增加一个深克隆的方法:
public interface StudentPro extends Cloneable,Serializable {
StudentPro clone();
StudentPro deepClone();
}
具体模型重写这方法:
/**
* 通过读写流实现
* @return
*/
@Override
public StudentPro deepClone() {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois;
ois = new ObjectInputStream(bis);
return (StudentPro) ois.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
s1保持一样,改变s2的克隆:
......
Student s2 = (Student) s1.deepClone();
s2.getLikes().add("打篮球");
s2.getCountry().setName("OOP");
结果:
D/TAG: s1: [哈哈, 2, 1, 广东, XXC, [rap, 跳]]
D/TAG: s2: [哈哈, 2, 1, 广东, OOP, [rap, 跳, 打篮球]]
D/TAG: s1: [哈哈, 2, 1, 广东, XXC, [rap, 跳]]
只有s2的值被修改了,s1没有被修改。
模式特点:
高效简便,性能优良。原型模式是在内存二进制流的复制,比直接new出一个对象性能要好。不需要做使用构造函数的准备工作,节省资源。同时这也是一个缺点,不走构造函数可能会导致准备工作不足。要根据所处环境选择。