设计模式之原型模式及在Android源码中的使用

原型模式

原型模式是一个创建型的模式.原型就是应该有一个样板实例,我们可以从这个样板对象中复制出一个内部属性一致的对象,其实就是一个”克隆”,被复制的实例就是我们所称”原型”,这个原型是可定制的.主要是原型模式多用于创建复杂的或者[构造耗时]的实例,因为这种情况下,复制一个已经存在的实例可使程序运行更加高效.

使用场景

  • 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用,这样也可以叫做保护性拷贝.
  • 通过new产生一个对象需要非常繁琐的数据准备或者访问权限.
  • 类初始化需要消化非常多的资源.

实现

首先我们得实现Cloneable接口,复写clone方法

1
2
3
4
5
6
7
8
9
10
11
12
13
public class User implements Cloneable{
....
@Override
protected User clone() {
User user = null;
try{
user = (User)super.clone();
} catch (CloneNotSupportedException e){
e.printStackTrace();
}
return user;
}
}

我们需要注意,clone这个方法不是Cloneable接口中的,我们来看Cloneable的定义,是一个空的接口:

1
2
3
4
5
6
7
* @author unascribed
* @see java.lang.CloneNotSupportedException
* @see java.lang.Object#clone()
* @since JDK1.0
*/
public interface Cloneable {
}

那么clone是哪来的呢,其实clone是Object中的方法,Cloneable是一个标识接口,它表明这个类的对象是可以拷贝的。如果没有实现Cloneable接口却调用了clone()函数将抛出异常。

浅拷贝和深拷贝

浅拷贝,也称为影子拷贝,这份拷贝实际上并不是将原始文档的所有字段都重新构造了一份,而是副本文档的字段引用原始文档的字段.
深拷贝, 其实就是拷贝时,我们的引用对象也得拷贝,更有甚者,就是不引用,创建一个全新的对象.

深拷贝栗子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public WordDocument implements Cloneable{
String mText;
ArrayList<String> mImages;
......
@Override
public WordDocument clone() {
try {
WordDocument doc = (WordDocument) super.clone();
doc.mText = this.mText;
doc.mImages = (ArrayList<String>) this.mImages.clone();
return doc;
} catch (CloneNotSupportedException e) {
}
return null;
}
}

如果是浅拷贝的话,A引用B就是说两个对象指向同一个地址,当修改A时B也会改变,B修改时A同样会改变。例如在浅拷贝的情况下,在List集合mImages中添加一项,则无论在A中还是在B中修改,两者都会同时改变,而深拷贝的话由于引用对象也得拷贝或者创建全新的对象,因此不会出现这种情况。

Android源码中的使用

ArrayList类中的使用

该类实现了Cloneable接口
clone方法为:

1
2
3
4
5
6
7
8
9
10
11
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);
}
}

可见拷贝的时候,引用对象也得拷贝

Bundle类中的使用

Bundle类,该类实现了Cloneable接口

1
2
3
4
5
6
7
8
public Object clone() {
return new Bundle(this);
}
public Bundle(Bundle b) {
super(b);
mFlags = b.mFlags;
}

拷贝时创建了一个全新的对象

Intent类中的使用

该类也实现了Cloneable接口
clone方法为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Override
public Object clone() {
return new Intent(this);
}
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;
this.mContentUserHint = o.mContentUserHint;
if (o.mCategories != null) {
this.mCategories = new ArraySet<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);
}
}

可见Intent拷贝时也创建了全新的对象

原型在源码中的应用还是很广的,不一一举例。

总结

使用原型模式可以解决构建复杂对象的资源消耗问题,能够在某些场景下提升创建对象的效率。还有一个用途就是保护性拷贝,也就是某个对象对外可能是只读的,为了防止外部对这个对象修改,通常可以返回一个对象拷贝的形式实现只读的限制。

优点

原型模式是内存中二进制流的拷贝,要比直接new一个对象性能好得多,特别是要在一个循环体内产生大量的对象时,原型模式就可以很好的体现其优点。

缺点

这既是它的优点也是它的缺点,直接在内存中拷贝,构造函数时不会执行的,在实际开发中应该注意这个潜在的问题。