原型模式的使用场景
(1)类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等,通过原型拷贝避免这些消耗。
(2)通过new一个对象需要非常繁琐的数据准备或访问权限,可以使用原型模式。
(3)一个对象需要提供给其他对象访问,而且各个调用者可能需要修改其值,可以考虑使用原型模式拷贝多个对象供调用者使用,即保护性拷贝
**注:**通过实现Cloneable接口的原型模式,在调用clone函数实例时比一定比new 操作要块,只有new构造对象非常耗时或者成本较高时,通过clone方法才能得到效率上的提升。因此在使用Cloneable时需要考虑构建对象成本以及效率上的一些测试。
文件类-WordDocument
public class WordDocument implements Cloneable {
//文本
private String mText;
//图片名列表
private ArrayList mImages = new ArrayList();
//原始拷贝方法 浅拷贝
// @Override
// protected WordDocument clone() {
// WordDocument document = null;
// try {
// document = (WordDocument) super.clone();
// document.mText = this.mText;
// document.mImages = this.mImages;
// return document;
// } catch (CloneNotSupportedException e) {
// e.printStackTrace();
// }
// return null;
// }
//深拷贝--建议使用
@Override
protected WordDocument clone() {
WordDocument document = null;
try {
document = (WordDocument) super.clone();
document.mText = this.mText;
//对yimage函数使用coloen
document.mImages = (ArrayList) this.mImages.clone();
return document;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
public WordDocument() {
System.out.println("----------WordDocument构造函数");
}
public String getmText() {
return mText;
}
public void setmText(String mText) {
this.mText = mText;
}
public ArrayList getmImages() {
return mImages;
}
public void addImages(String img) {
this.mImages.add(img);
}
public void showDocument() {
System.out.println("word content start");
System.out.println("Text:" + mText);
System.out.println("imagesList:");
for (String imageName : mImages) {
System.out.println("image name:" + imageName);
}
System.out.println("word content end");
}
}
输出类-client
public class Client {
public static void main(String[] args) {
//1构建文档对象
WordDocument wordDocument = new WordDocument();
//2编辑文档添加图片等
wordDocument.setmText("这是一篇文档");
wordDocument.addImages("图片一");
wordDocument.addImages("图片二");
wordDocument.addImages("图片三");
wordDocument.showDocument();
//以原始文档为原型,拷贝一份副本
WordDocument doc = wordDocument.clone();
doc.showDocument();
//修改副本不会影响原始文档
doc.setmText("修改doc文本");
doc.addImages("哈哈.jpg");
doc.showDocument();
wordDocument.showDocument();
}
}
总结
浅拷贝遇到的问题
说明 WordDocument的clone方法只是简单的进行浅拷贝,引用类型的新对象doc的mImage只是单纯的指向了this.mImages的引用,并没有重新构造mImages对象,然后将原始文档中的图片添加到新的mImages对象中,这样导致doc中的mImages与原始文件中的是同一个对象,因此修改了其中一个文档中的图片,另一个文档也会受到影响.
解决方法:采用深拷贝 即–在拷贝对象时,对引用型的字段也要采用拷贝的形式,而不是单纯的引用形式.
源码使用场景-Intent
Intent用于跳转Activity、启动服务、发布广播等功能,它是Android系统各个组件之间的纽带,也是组件之间传递数据的载体,正式Intent的存在才使得Android各个组件之间的耦合性很低。
发信息操作
Uri uri = Uri.parse("smsto:13426074245");
Intent intent = new Intent(Intent.ACTION_SENDTO, uri);
intent.putExtra("sms_body", "send content");
Intent sendTo = (Intent) intent.clone();
startActivity(sendTo);
上述代码中构建了一个发短信的Intent对象,并设置了发短信的内容,通过原始Intent的clone()方法构建一个Intent副本,在使用副本进行发短信操作。
源码
@Override
public Object clone() {
return new Intent(this);
}
/**
* Copy constructor.
*/
public Intent(Intent o) {
this(o, COPY_MODE_ALL);
}
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方法实际上在内部并没有调用super.clone()方法来实现对象拷贝,而是调用了new Intent(this)。这个在上文中提到过,使用clone 和new 需要根据构造对象的成本来决定,如果对象的构造成本比较高或者构造比较麻烦,那么使用clone()函数效率较高,否则可以使用new 的形式。(这就和C++的拷贝构造函数完全一致),将原始对象作为构造函数的参数,然后在构造函数内将原始对象的数据逐个拷贝一遍。到此克隆过程就完成了。
Android系统启动之后就会注册各种系统服务,入WindowManagerService、ActivityManagerService等,其中就有一个PackageManagerService(简称PMS)。PMS启动后会扫描系统中已安装的apk目录,例如系统APP的安装目录为/system/aPP,第三方应用的目录为/data/aPP,PMS会解析apk包下的AndroidManifest.xml文件得到APP相关信息,而每个AndroidManifest.xml又包含了Activity、Service等组件的注册信息,当PMS扫描并且解析完这些信息之后就构建好了这个app的信息树.
案例demo
public class User{
public int age;
public String name;
public String phoneNum;
public Adress adress;
@Override
public String toString() {
return "User[aget=" + age + ",name=" + name + ",phoneNum=" + phoneNum + "adress=" + adress + "]";
}
}
public class LoginImpl implements Login {
public LoginImpl() {
longin();
}
//登录成功调用 为了简化,在初始化时就调用了
@Override
public void longin() {
User user = new User();
user.age = 22;
user.name = "Mr.Simple";
user.adress = new Adress("北京", "朝阳", "四惠");
LoginSession.getLoginSession().setLoginUser(user);
}
}
public class LoginSession {
static LoginSession loginSession = null;
private User loingUser;
private LoginSession() {}
public static LoginSession getLoginSession() {
if (loginSession == null) {
loginSession = new LoginSession();
}
return loginSession;
}
void setLoginUser(User user) {
loingUser = user;
}
public User getLoingUser() {
return loingUser;
}
}
执行
new LoginImpl();
System.out.println(LoginSession.getLoginSession().getLoingUser().toString());
User user = LoginSession.getLoginSession().getLoingUser();
user.adress = new Adress("name", "obname", "ssname");
System.out.println(LoginSession.getLoginSession().getLoingUser().toString());
输出:
User[aget=22,name=Mr.Simple,phoneNum=nulladress=Adress[ city=北京,district=朝阳,street=四惠]]
User[aget=22,name=Mr.Simple,phoneNum=nulladress=Adress[ city=name,district=obname,street=ssname]]
修改数据的引用是一样的 即便没有通过构造修改参数也会生效
使用原型模式clone出一个备份使用,即便客户端无意识修改了,对保存参数的类也不会影响修改如下:
user修改
public class User implements Cloneable {
public int age;
public String name;
public String phoneNum;
public Adress adress;
//添加clone方法
@Override
protected User clone() {
User user = null;
try {
user = (User) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return user;
}
@Override
public String toString() {
return "User[aget=" + age + ",name=" + name + ",phoneNum=" + phoneNum + "adress=" + adress + "]";
}
}
public class LoginSession {
static LoginSession loginSession = null;
private User loingUser;
private LoginSession() {
}
public static LoginSession getLoginSession() {
if (loginSession == null) {
loginSession = new LoginSession();
}
return loginSession;
}
void setLoginUser(User user) {
loingUser = user;
}
//使用clone函数
public User getLoingUser() {
return loingUser.clone();
}
}
优点缺点对比
优点: 原型模式是在内存中二进制流的拷贝,要比直接new 一个对象性能好很多,特别是要在一个循环体内产生大量的对象时,原型模式可以更好的体现其有点
缺点: 它的优点也是缺点,直接在内存汇总拷贝,构造函数是不会执行的,在实际开发中,应该注意这个潜在的问题.优点是减少了约束,缺点也是减少了约束,需要再时机应用的时候考虑
参考–android源码设计模式解析与实战