定义
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
主要解决什么
在运行期建立和删除原型。
何时使用
- 当一个系统应该独立于它的产品创建,构成和表示时。
- 当要实例化的类是在运行时刻指定时,例如,通过动态装载。
- 为了避免创建一个与产品类层次平行的工厂类层次时。
- 当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。
如何解决
利用已有的一个原型对象,快速地生成和原型对象一样的实例。
关键代码
- 实现克隆操作,在 JAVA 实现 Cloneable,重写 clone(),在 .NET 中可以使用 Object 类的 MemberwiseClone() 方法来实现对象的浅拷贝或通过序列化的方式来实现深拷贝。
- 原型模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些"易变类"拥有稳定的接口。
怎么实现浅拷贝
以JAVA为例,实现Cloneable 接口,并重写clone方法。
这里就不贴代码了。
怎么实现深拷贝
使用clone方法 (需要实现Cloneable接口)
public class DeepProtoType implements Serializable, Cloneable {
private String name;
private DeepCloneableTarget deepCloneableTarget;
//get set 方法这里就不写了
//深拷贝- 方式1 使用clone方法
@Override
protected Object clone() throws CloneNotSupportedException {
Object deep = null;
//完成对基本数据类型和String的克隆
deep = super.clone();
//对引用类型的属性,进行单独处理。DeepCloneableTarget是辅助实现的。
// 如果一个类的引用类型很多, 这种方式则需要一个一个处理。故通过序列化的方式来实现深拷贝
DeepProtoType deepProtoType = (DeepProtoType) deep;
deepProtoType.deepCloneableTarget = (DeepCloneableTarget) deepCloneableTarget.clone();
return deep;
}
}
客户端使用
public static void main(String[] args) throws CloneNotSupportedException {
DeepProtoType p = new DeepProtoType();
p.setName("小明");
p.setDeepCloneableTarget(new DeepCloneableTarget("小红"));
//方式一 完成深拷贝
DeepProtoType p2 = (DeepProtoType) p.clone();
System.out.println(p.getDeepCloneableTarget().hashCode()); //输出结果356573597
System.out.println(p2.getDeepCloneableTarget().hashCode()); //输出结果1735600054
}
根据输出结果可见, 两个对象的引用类型的成员变量不是相同的, 而是一个新的对象, 可证明深复制成功.
通过对象的序列化实现 (需要实现Serializable接口) 推荐使用
public class DeepProtoType implements Serializable, Cloneable {
private String name;
private DeepCloneableTarget deepCloneableTarget;
//get set 方法这里就不写了
// 深拷贝 -方式2 通过对象的序列化实现(推荐)
public Object deepClone() {
//创建流对象
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
try {
//序列化
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this);
//反序列化
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
DeepProtoType o = (DeepProtoType) ois.readObject();
return o;
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
try {
bos.close();
oos.close();
bis.close();
ois.close();
} catch (Exception e2) {
System.out.println(e2.getMessage());
}
}
}
}
客户端使用
public static void main(String[] args) throws CloneNotSupportedException {
DeepProtoType p = new DeepProtoType();
p.setName("小明");
p.setDeepCloneableTarget(new DeepCloneableTarget("小红"));
//方式二 完成深拷贝
DeepProtoType p2 = (DeepProtoType) p.deepClone();
System.out.println(p.getDeepCloneableTarget().hashCode()); //输出结果621009875
System.out.println(p2.getDeepCloneableTarget().hashCode()); //输出结果2065951873
}
优点: 1、性能提高。 2、逃避构造函数的约束。
缺点: 1、配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。 2、必须实现 Cloneable 接口。
Android中的应用
Bitmap中的copy方法、Java中的clone方法和Animator等。
下面以 Intent 为例来分析源码中的原型模式。
5668 /**
5669 * Copy constructor.
5670 */
5671 public Intent(Intent o) {
5672 this(o, COPY_MODE_ALL);
5673 }
5674// CopyMode 是Android 8.1 之后增加的。
5675 private Intent(Intent o, @CopyMode int copyMode) {
5676 this.mAction = o.mAction;
5677 this.mData = o.mData;
5678 this.mType = o.mType;
5679 this.mPackage = o.mPackage;
5680 this.mComponent = o.mComponent;
5681
5682 if (o.mCategories != null) {
5683 this.mCategories = new ArraySet<>(o.mCategories);
5684 }
5685
5686 if (copyMode != COPY_MODE_FILTER) {
5687 this.mFlags = o.mFlags;
5688 this.mContentUserHint = o.mContentUserHint;
5689 this.mLaunchToken = o.mLaunchToken;
5690 if (o.mSourceBounds != null) {
5691 this.mSourceBounds = new Rect(o.mSourceBounds);
5692 }
5693 if (o.mSelector != null) {
5694 this.mSelector = new Intent(o.mSelector);
5695 }
5696
5697 if (copyMode != COPY_MODE_HISTORY) {
5698 if (o.mExtras != null) {
5699 this.mExtras = new Bundle(o.mExtras);
5700 }
5701 if (o.mClipData != null) {
5702 this.mClipData = new ClipData(o.mClipData);
5703 }
5704 } else {
5705 if (o.mExtras != null && !o.mExtras.maybeIsEmpty()) {
5706 this.mExtras = Bundle.STRIPPED;
5707 }
5708
5709 // Also set "stripped" clip data when we ever log mClipData in the (broadcast)
5710 // history.
5711 }
5712 }
5713 }
5714
5715 @Override
5716 public Object clone() {// 重写clone
// clone 方法实际上在内部并没有调用 super.clone() 来实现拷贝对象,而是通过 new Intent(this)。
5717 return new Intent(this);
5718 }
使用 clone 和 new 需要根据构造对象的成本来决定,如果对象的构造成本比较高或者构造麻烦,那么使用 clone 函数效率较高,反之可以使用 new 关键字的形式。
这和 C++ 中的 copy 构造函数完全一致,将原始对象作为构造函数的参数,然后在构造函数内将原始对象数据挨个 copy。
使用场景
- 资源优化场景。
- 类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
- 性能和安全要求的场景。
- 通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
- 一个对象多个修改者的场景。
- 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。
- 在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与 Java 融为浑然一体,大家可以随手拿来使用。