先上定义
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
Prototype原型模式是一种创建型设计模式,Prototype模式允许一个对象再创建另外一个可定制的对象,根本无需知道任何如何创建的细节,工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建。
这些定义讲得对极了,但是对于初学者而言,我觉得是灾难。这种学术性的东西是用来总结的,而不是用来引导启蒙。而最好的方式,应该是大白话,举生动的例子,毕竟软件的出现就是为了解决特定场景的需求。
原型模式就是复制–粘贴
这是网络上很流行的一个比喻。说得对极了。下面我的文章也就是为了更进一步帮大家加深印象。
场景
中国人引以自豪的“四大发明”中有这么一项–活字印刷术。
活字印刷术是一种古代印刷方法,是中国古代劳动人民经过长期实践和研究才发明的。先制成单字的阳文反文字模,然后按照稿件把单字挑选出来,排列在字盘内,涂墨印刷,印完后再将字模拆出,留待下次排印时再次使用。
字模的出现,解放了生产力,节省了大量的物力和人力。而字模之间有什么差别呢?字模有同样的规格,同样的材料,唯一不同的就是字模上面的字不同。因此生产字模的时候可以用同一个模具,这里面就有原型模式的思想在里面。模具产生一个个几乎相同的字模,这相当于克隆,然后改变每个字模上面的字,于是字模就独一无二了。但归根结底,它们仍旧是同一类事物,通过模具克隆生产,比重新一个个特别制作要省时省心。
再来一个例子,在java世界中创建一个对象通常用new关键字就可以搞定。比如一个教师,它的属性有名字、地址、身高、课程。
class Teacher{
String name;
String addr;
int height;
ArrayList<String> courses;
...
}
我们创建一个教师实例自然是new的形式。
Teacher a = new Teacher();
Teacher b = new Teacher();
Teacher c = new Teacher();
Teacher d = new Teacher();
这似乎没有什么。
那好,要给教师设立属性,但是这些老师都是一个学校的,很多属性一样。有什么问题吗?没有什么问题
a.setName("zhangsan");
a.setAddr("shenzhen");
a.setSchool("xxx");
a.setHeight(170);
b.setName("lisi");
b.setAddr("shenzhen");
b.setSchool("xxx");
b.setHeight(170);
c.setName("wangwu");
c.setAddr("shenzhen");
c.setSchool("xxx");
c.setHeight(170);
d.setName("zhaoliu");
d.setAddr("shenzhen");
d.setSchool("xxx");
d.setHeight(170);
似乎还可以接受,这四个老师其它的属性都一样,连身高都一样。如果我再创建10个同样的老师对象呢?他们还是就名字不一样。你受得了吗?我可受不了,这个时候,主人公原型模式就出来了。
而在java中cloneable很容易实现。我们让Teacher类实现Cloneable接口即可。
class Teacher implements Cloneable{
String name;
String school;
String city;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSchool() {
return school;
}
public void setSchool(String school) {
this.school = school;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public Teacher(String name, String school, String city) {
super();
this.name = name;
this.school = school;
this.city = city;
}
@Override
protected Object clone(){
// TODO Auto-generated method stub
Teacher teacher = null;
try {
teacher = (Teacher) super.clone();
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return teacher;
}
//
@Override
public String toString() {
return "Teacher [name=" + name + ", school=" + school + ", city="
+ city + "]";
}
}
接下来,我们创建4个老师对象,只需要这样。
public class Test {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Teacher a = new Teacher("zhangsan","beida","beijing");
Teacher b = (Teacher) a.clone();
Teacher c = (Teacher) a.clone();
Teacher d = (Teacher) a.clone();
b.setName("lisi");
c.setName("wangwu");
d.setName("zhaoliu");
System.out.println(a.toString());
System.out.println(b.toString());
System.out.println(c.toString());
System.out.println(d.toString());
}
}
结果如下
Teacher [name=zhangsan, school=beida, city=beijing]
Teacher [name=lisi, school=beida, city=beijing]
Teacher [name=wangwu, school=beida, city=beijing]
Teacher [name=zhaoliu, school=beida, city=beijing]
是不是很爽?尝到甜头了吧。
对于大量的重复的同一类对象,我们可以用原型模式解放自己(确实少敲了好多行代码)。系统也减少开销。但我们需要处理一种情况,比如Teacher中有一个列表是代表这个老师教了几种科目,小时候学校师资力量有限,一个老师经常同时教几门,不是经常说的小学的数学是体育老师教的么?那好,当存在集合对象的类进行原型克隆的时候会发生什么现象?
class Teacher implements Cloneable{
String name;
String school;
String city;
ArrayList<String> courses;
public ArrayList<String> getCourses() {
return courses;
}
public void setCourses(ArrayList<String> courses) {
this.courses = courses;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSchool() {
return school;
}
public void setSchool(String school) {
this.school = school;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public Teacher(String name, String school, String city) {
super();
this.name = name;
this.school = school;
this.city = city;
}
@Override
protected Object clone(){
// TODO Auto-generated method stub
Teacher teacher = null;
try {
teacher = (Teacher) super.clone();
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return teacher;
}
@Override
public String toString() {
return "Teacher [name=" + name + ", school=" + school + ", city="
+ city + ", courses=" + courses + "]";
}
}
我们再来原型拷贝
public class Test {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Teacher a = new Teacher("zhangsan","beida","beijing");
ArrayList<String> courses = new ArrayList<>();
courses.add("math");
courses.add("chinese");
a.setCourses(courses);
Teacher b = (Teacher) a.clone();
Teacher c = (Teacher) a.clone();
Teacher d = (Teacher) a.clone();
b.setName("lisi");
c.setName("wangwu");
d.setName("zhaoliu");
b.getCourses().add("music");
System.out.println(a.toString());
System.out.println(b.toString());
System.out.println(c.toString());
System.out.println(d.toString());
}
}
我们预想中的是a、c、d课程一样,b不同,b要多一个music。那好,看结果
Teacher [name=zhangsan, school=beida, city=beijing, courses=[math, chinese, music]]
Teacher [name=lisi, school=beida, city=beijing, courses=[math, chinese, music]]
Teacher [name=wangwu, school=beida, city=beijing, courses=[math, chinese, music]]
Teacher [name=zhaoliu, school=beida, city=beijing, courses=[math, chinese, music]]
吓了一跳!!!是不是?怎么回事呢?
这里要解释一下,涉及到两个概念,浅拷贝和深拷贝。
浅拷贝:使用一个已知实例对新创建实例的成员变量逐个赋值,这个方式被称为浅拷贝。
深拷贝:当一个类的拷贝构造方法,不仅要复制对象的所有非引用成员变量值,还要为引用类型的成员变量创建新的实例,并且初始化为形式参数实例值。
原型模式一般都是对引用的赋值,基础数据还好,但如果存在如ArrayList这种集合变量时,复制过去的只是它的引用,这样就会造成,对一处改变,其它拥有相同引用的对象也都改变,所以上面所有的课程都变成了一样。
解决方案
很简单,重写clone()方法的时候,将ArrayList变量也进行深拷贝。如下
@Override
protected Object clone(){
// TODO Auto-generated method stub
Teacher teacher = null;
try {
teacher = (Teacher) super.clone();
teacher.courses = (ArrayList<String>) this.courses.clone();
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return teacher;
}
再看结果
Teacher [name=zhangsan, school=beida, city=beijing, courses=[math, chinese]]
Teacher [name=lisi, school=beida, city=beijing, courses=[math, chinese, music]]
Teacher [name=wangwu, school=beida, city=beijing, courses=[math, chinese]]
Teacher [name=zhaoliu, school=beida, city=beijing, courses=[math, chinese]]
正常了。
Android中的原型模式
Intent
public class Intent implements Parcelable, Cloneable {
......
public Intent(Intent o) {
this.mAction = o.mAction;
this.mData = o.mData;
this.mType = o.mType;
this.mPackage = o.mPackage;
this.mComponent = o.mComponent;
this.mFlags = o.mFlags;
if (o.mCategories != null) {
this.mCategories = new HashSet<String>(o.mCategories);
}
if (o.mExtras != null) {
this.mExtras = new Bundle(o.mExtras);
}
if (o.mSourceBounds != null) {
this.mSourceBounds = new Rect(o.mSourceBounds);
}
if (o.mSelector != null) {
this.mSelector = new Intent(o.mSelector);
}
if (o.mClipData != null) {
this.mClipData = new ClipData(o.mClipData);
}
}
@Override
public Object clone() {
return new Intent(this);
}
......
}
可以看到Intent实现了Cloneable接口,并重写了clone()方法,并且进行的是深度克隆。基础类型的变量直接赋值。需要尝试复制的则直接创建新对象,避免克隆出来的对象和自身共用同一个变量的引用。
总结
原型模式适合的场景,大都是用来创建重复的同类型但属性稍微不同的对象,但缺点就是要考虑深克隆的场景,如果一个类很复杂,里面有许多需要深度克隆的变量引用时,那么对于这个类而言,进行克隆的时候要妥善处理好变量中的深度克隆。
实际开发中要结合实际情况而定。