简介
指从一个样板对象中复制出一个内部属性一致的对象,其实就是克隆。而被复制的对象就叫做原型,多用于创建复杂的或者构造耗时的实例
定义
用原型实例指定创建对象的种类, 并通过拷贝这些原型创建新的对象.
场景
- 类初始化需要消耗非常多的资源, 这个资源包括数据,硬件资源等, 可通过原型拷贝避免这些消耗
- 通过 new 产生一个对象需要非常繁琐的数据准备或访问权限, 同样可以使用原型模式
- 一个对象需要提供给其他对象访问, 并且会能会对其修改属性, 可以用原型拷贝多个对象提供使用
使用 Cloneable接口 , Objecct.clone()方法
- 先看下Object.clone()方法说明
表示:创建一个对象的副本。注意,clone不同于赋值,赋值是对象的引用传递,而clone是则创建一个与原型有相同数据的对象。
protected native Object clone() throws CloneNotSupportedException;
看下clone后的区别
x.clone() != x; //可以看到clone后的对象与原型不是同一个引用
x.clone().getClass() == x.getClass(); //但还是同个对象类型
x.clone().equals(x) == true; //数据一样
如果一个对象使用object.clone的时候没有实现Cloneable接口,则会抛出一个ClassNotSupportedException异常。
此外,所有的array数组默认都已经实现了Cloneable接口
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
- 例子
public class WordDocument implements Cloneable{
// 文本
public String mText;
// 图片名列表
public ArrayList<String> mImages = new ArrayList<String>();
@Override
protected WordDocument clone() {
try {
// 通过本地方法特殊途径, 构建一个对象
WordDocument doc = (WordDocument) super.clone();
//mText 虽然是对象, 但是因为是 String 类型, 属于安全类型, 由于final类,实例不可更改的特性. 如果对副本进行字符串的修改, 只不过是把原引用删除,重新指向了新的字符串.
doc.mText = this.mText;
// 因为Image是引用类型, 这样直接赋值属于浅拷贝, 再次对集合进行clone. 实现wordDocument的深拷贝
doc.mImages = (ArrayList<String>) this.mImages.clone();
// 这里进行修改 那么此时属于浅拷贝
//doc.mImages = this.mImages;
return doc;
}catch (Exception ex){}
return null;
}
}
本质也就是类实现Cloneable接口,再通过super.clone()构造有一个本类对象的一个初始状态,然后给被拷贝的对象赋值而已。
这里区别一下浅拷贝和深拷贝
* 浅拷贝(影子拷贝):拷贝出来的对象并不是完全的一份独立对象。新的对象属性中引用了原始对象中的数据。如代码中 doc.mImages = this.mImages 可以看到这里只是引用赋值了而已
* 深拷贝:拷贝出来的对象的属性与原始对象没有关联,是完全独立开来的一个副本。如代码中 doc.mImages = (ArrayList) this.mImages.clone();
拷贝
拷贝对象一般有两种方法,第一种就是new一个新的对象,第二种就是clone。
当new构造对象比较耗时和成本较高时,通过clone就可以获得效率提升
Android源码
@Override
public Object clone() {
return new Intent(this);
}
/**
* Copy constructor.
*/
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进行数据复制,成本比较低
实战
当登录模块登录成功之后, 会把一些个人信息,token等信息在保存类中的某个数据结构上, 并通过一个方法对外暴露出去, 提供其他模块使用. 但是如果你返回的是一个数据结构也就是一个对象, 这个对象包含了很多个人信息, 但是正常来说, 对于外部应该只提供查看数据的能力, 不应该提供修改的能力.
也就是克隆一个对象给你读取数据,尽管它修改了数据,但毕竟不是修改在原始的数据上
所以这个使用, 就可以对登录模块对外暴露的方法进行修改, 利用 原型模式 对外返回的是一个内部数据的 深拷贝 , 这样就把可能出现的隐患彻底的隔绝了.
注意
原型模式 是通过内存中二进制流的方式拷贝, 要比直接通过 new 一个对象性能更好, 特别是循环体内产生大量对象是. 但是注意, 因为是 二进制流的拷贝 , 所以构造函数是不会执行的. 这点要明确记牢.