一、原型模式ProtoType简介
基于指定的原型对象创建新对象。其proto跟original同义。
uml类图非常简单,原型类需要实现Cloneable接口,当然这里cloneable是一个
标志接口。重写Object类的clone方法。
public interface Cloneable { // 只是一个标志接口
}
二、原型模式关键点:原型类的clone()实现
2.1、浅拷贝
如果一个model的成员属性全部都是基本数据类型,那都是浅拷贝。
如果成员是String引用类型,String比较特殊,它虽然是引用类型,但String没有重写clone方法,所以不能调用clone方法,可当做基本数据类型使用,当修改clone对象的String类型的值,该成员就会指向另外的String对象,不会影响到原型对象的值。
@Override
protected WordDoc clone() throws CloneNotSupportedException {
return (WordDoc) super.clone();
}
如果成员是其他引用类型,修改创建的对象的值,原型对象的该值也会跟着变化。二者引用指向同一块内存区域,地址传递。
2.2、深拷贝
除String类的其他引用类型,则需要深拷贝,才是我们原型模式需要关注的点。下面以ArrayList对象为例,其实现的clone方法:
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
// 复制数组数据
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
// native进行复制,不是赋值,是复制
public static native void arraycopy(Object src, int srcPos,Object dest, int destPos,
int length);
所以针对引用类型,我们直接使用java封装好的clone方法即可。
@Override
protected WordDoc clone() {
try {
// 如果WordDoc的成员全部是基本类型,那可以直接浅拷贝就可以
// 如果存在引用类型,则需要深拷贝
WordDoc doc = (WordDoc) super.clone();
//这里的ArrayList就是深拷贝 调用其clone方法。
doc.mImages = (ArrayList<String>) this.mImages.clone();
return doc;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
三、完整的例子
public class WordDoc implements Cloneable {
private String mText;
private ArrayList<String> mImages = new ArrayList<>();
public WordDoc() {
System.out.println("构造函数");
}
public String getText() {
return mText;
}
public void setText(String text) {
mText = text;
}
public ArrayList<String> getImages() {
return mImages;
}
public void addImages(String url) {
mImages.add(url);
}
public void show() {
System.out.println("--------------" + this + "------------");
System.out.println("text-->" + mText);
for (String img : mImages) {
System.out.println(img);
}
}
@Override
protected WordDoc clone() {
try {
// 如果WordDoc的成员全部是基本类型,那可以直接浅拷贝就可以
// 如果存在引用类型,则需要深拷贝
WordDoc doc = (WordDoc) super.clone();
//这里的ArrayList就是深拷贝
doc.mImages = (ArrayList<String>) this.mImages.clone();
return doc;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
client调用:
public static void main(String[] args) {
WordDoc doc = new WordDoc();
doc.setText("a");
doc.addImages("http://url1");
WordDoc doc2 = doc.clone();
doc2.addImages("http://url2");
doc.getImages().remove("http://url1");
doc.show();
doc2.show();
}
log日志如下:
只打印了一次构造函数,说明clone对象时并不会走构造。
当有除String外的引用类型成员时,使用深拷贝,原型对象和clone创建的对象二者不会相互影响。
四、从log得出使用场景
4.1、如果创建对象时走构造比较耗费资源时,再次创建该类对象,可以考虑改模式。
4.2、深拷贝出来的对象不会对原型对象有影响。当有多个调用方时,只有1份原型对象,但每个调用方都会对这个对象进行操作,那为了避免影响,可以针对每个调用方clone一个对象,执行操作后,返回给调用方。原型对象则还是最初的那一份。这里就是所谓的保护性拷贝。
最后,特别强调下,调用clone方法的话,则一定要实现Cloneable接口且实现clone方法,缺一不可。