上篇博客学习了Build模式,本篇博客学习一下原型模式,其实说到原型模式,刚开始听的时候一脸懵逼,但是学习完了之后,真的是感觉简单。
原型模式介绍
原型模式是创建型模式的一种,其实就是想要以某一个对象为“模版”,“复制”出相同的对象,也就是“克隆”出一摸一样的对象。原型模式多用于需要构建复杂的对象时使用。因为此时“复制”一个对象比创建new一个对象效率更高。
原型模式定义
用原型对象的实例执行创建对象的种类,并通过拷贝创建出新的对象
原型模式的使用场景
- 创建new一个对象需要消耗非常多的资源,这个资源包括数据,硬件的消耗等,通过复制拷贝的方式避免过的消耗资源
- 一个对象可能在多个地方使用,并且使用的地方都可能修改这个对象的属性值,此时应该拷贝若干份对象,给不同的调用者使用
- 通过new创建对象需要复杂的数据准备或者数据设置,这时可以直接拷贝复制对象。
一般来说,我们实现原型模式的方法有两种:
- 原型对象实现Cloneable接口,然后在clone方法内,调用父类的对象clone方法拷贝对象。
- 不使用Cloneable接口,自行在clone方法内处理逻辑
原型模式的简单实现(实现Cloneable接口)
示例:假设现在我们正在写一个文档Document,文档中包含图片和文字,用户写了很长的文档,此时需要修改里面的东西,但是呢,我们又不确定修改编辑后的内容是否会被采用,此时我们就可以拷贝一份文档Document,这就是典型的:原型模式。
原型
public class Document implements Cloneable{
private String content; //内容
private ArrayList<String> imageList = new ArrayList<>(); //图片
public Document() {
Log.e("Document:", "构造方法");
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public List<String> getImageList() {
return imageList;
}
public void addImage(String image) {
this.imageList.add(image);
}
@Override
protected Document clone() {
Document documentCopy = null;
try{
documentCopy = (Document) super.clone();
documentCopy.content = this.content;
documentCopy.imageList = this.imageList;
} catch (CloneNotSupportedException e) {
}
return documentCopy;
}
@Override
public String toString() {
String diver = "\n------------start-------------\n";
String contentString = "content = " + this.content + " \n";
String imageString = "";
for(String imageValue : imageList) {
imageString += "图片:" + imageValue + " \n";
}
String end = "------------end-------------\n";
Log.e("DocumentL:", diver + contentString + imageString + end);
return diver + contentString + imageString + end;
}
}
在原型Document中,我们实现了Cloneable接口,在clone方法内,拷贝了Document对象
Document document = new Document();
document.setContent("我是内容");
document.addImage("我是图片1");
document.addImage("我是图片2");
document.toString();
//clone
Document documentClone = document.clone();
documentClone.setContent("我是修改后的内容");
documentClone.addImage("我是图片3");
documentClone.toString();
//再次打印原版
document.toString();
拷贝的Document对象修改了文本内容,但是原型对象文本内容并没有被修改。 但是呢,细心的同学可能发现了,拷贝的对象添加了一张图片,而原型对象中的图片也随之被修改了(添加了)。这是为什么?
深拷贝和浅拷贝
出现上述问题的原因是因为是属于:浅拷贝。
关于深拷贝和浅拷贝,用C++的地址内存来说的话就是:不管是基本数据类型还是引用数据类型,都会在栈中分配一块内存,基本数据类型在栈中的那块内存存储的直接是它的值,而引用数据类型在栈中存储的是一个地址(相当于一个指针),而这个地址指向了堆中的真正的值。也就是说:引用数据类型真正的值是存放在堆内存中的。
浅拷贝:只用把自己赋值给新对象,而不管自己是什么类型(引用数据类型还是基本数据类型);
深拷贝:如果是引用数据类型,就调用起自己的clone方法拷贝,如果是基本数据类型就直接赋值。
还记得我们最早学习Java的时候的:值传递和引用传递吗?对,就是这个东西。我们的文本内容是:基本数据类型,而图片对象是:引用数据类型。此时我们的引用数据类型就相当于是浅拷贝,也就是说:
//直接复制的是值
documentCopy.content = this.content;
//直接传递的是数组的引用
documentCopy.imageList = this.imageList;
知道原因后,我们修改clone方法如下:
@Override
protected Document clone() {
Document documentCopy = null;
try{
documentCopy = (Document) super.clone();
ArrayList<String> imageListClone = (ArrayList<String>) this.imageList.clone();
documentCopy.content = this.content;
documentCopy.imageList = imageListClone;
} catch (CloneNotSupportedException e) {
}
return documentCopy;
}
由于ArrayList类本身实现了Cloneable接口,所以可以直接调用其clone方法复制对象。此时打印结果正确。
原型模式的其它实现方式
Intent的使用相信是非常熟悉的,看如下代码:
Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.parse("smsto:123456"));
intent.putExtra("sms_body", "Hello world!");
Intent cloneIntent = (Intent) intent.clone();
startActivity(cloneIntent);

@Override
public Object clone() {
return new Intent(this);
}
private Intent(Intent o, @CopyMode int copyMode) {
this.mAction = o.mAction;
this.mData = o.mData;
this.mType = o.mType;
this.mPackage = o.mPackage;
this.mComponent = o.mComponent;
if (o.mCategories != null) {
this.mCategories = new ArraySet<>(o.mCategories);
}
if (copyMode != COPY_MODE_FILTER) {
this.mFlags = o.mFlags;
this.mContentUserHint = o.mContentUserHint;
this.mLaunchToken = o.mLaunchToken;
if (o.mSourceBounds != null) {
this.mSourceBounds = new Rect(o.mSourceBounds);
}
if (o.mSelector != null) {
this.mSelector = new Intent(o.mSelector);
}
if (copyMode != COPY_MODE_HISTORY) {
if (o.mExtras != null) {
this.mExtras = new Bundle(o.mExtras);
}
if (o.mClipData != null) {
this.mClipData = new ClipData(o.mClipData);
}
} else {
if (o.mExtras != null && !o.mExtras.maybeIsEmpty()) {
this.mExtras = Bundle.STRIPPED;
}
// Also set "stripped" clip data when we ever log mClipData in the (broadcast)
// history.
}
}
}
在clone的方法内,直接创建了Intent对象。而没有使用拷贝的方法创建,因此我们可以知道,在实际项目中,我们要根据自身情况看是否要:创建对象。
源码示例地址:
GitHub示例源码
GitHub示例源码
GitHub示例源码