您的位置:首页 > 移动开发 > Android开发

三、原型设计模式

2016-04-25 21:14 489 查看

1. 原型设计模式介绍

用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。原型设计模式多用于创建复杂的或者构造耗时的实例。因为这种情况下,复制一个已经存在的实例可使程序运行更高效。

2. 原型设计模式使用场景

类初始化需要消耗非常多的资源,这个资源包括数据、硬件资源等,通过原型拷贝避免这些消耗。

通过new产生一个对象需要非常繁琐的数据准备或访问权限,这时可以使用原型模式。

一个对象需要提供给其它对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式供多个对象供调用者使用,即保护性拷贝。

3. 原型设计模式的UML类图



4. 原型设计模式的简单实现

模拟情形: 用户首先创建了一个文档对象,即WordDocument,这个文档中包含文字和图片。用户经过了长时间的内容编辑后,打算对该文档做进一步的编辑,这个编辑后的文档是否会被采用还不去确定,因此为了安全起见,用户需要将当前文档拷贝一份,然后再在副本上进行修改。

WordDocument类:

public class WordDocument implements Cloneable {
//文本
private String mText;

//图片
private ArrayList<String> mImages = new ArrayList<String>();

public WordDocument() {
System.out.println("---------WordDocument构造函数--------");
}

@Override
protected Object clone() throws CloneNotSupportedException {
try {
WordDocument wordDocument = (WordDocument) super.clone();
wordDocument.mImages = this.mImages;
wordDocument.mText = this.mText;
return wordDocument;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}

public String getmText() {
return mText;
}

public void setmText(String mText) {
this.mText = mText;
}

public ArrayList<String> getmImages() {
return mImages;
}
/**
* 添加图片
* @param image
*/
public void addImage(String image) {
mImages.add(image);
}

/**
* 打印文档内容
*/
public void showDocument() {
System.out.println("-------WordContent Start---- ");
System.out.println("mText:" + mText);
System.out.println("Images List:" + mImages);
for (String imageName : mImages) {
System.out.println("image name:" + imageName);
}
System.out.println("------WordContent End-----");
}
}


测试Client端:

public class Client {
public static void main(String[] args) {
//1.构建文档对象
WordDocument wordDocument = new WordDocument();

//2. 编辑文档、添加图片等
wordDocument.setmText("这是一篇文档");
wordDocument.addImage("图片1");
wordDocument.addImage("图片2");
wordDocument.addImage("图片3");
wordDocument.addImage("图片4");

//以原始对象为原型,拷贝一份副本
try {
WordDocument cloneWord = (WordDocument) wordDocument.clone();
cloneWord.showDocument();

//修改文档副本,不会影响原始文档
cloneWord.setmText("这是修改后的拷贝文档");
cloneWord.showDocument();
cloneWord.addImage("哈哈哈.jpg");

} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}


WordDocument对象实现了Cloneable接口,表示这个类的对象是可以拷贝的。如果没有实现Cloneable接口却调用了clone()函数将抛出异常。

通过clone拷贝对象时并不会执行构造函数,因此如果在构造函数中需要一些特殊的初始化操作的类型,在使用Coneable实现拷贝时,需要注意构造函数不会执行的问题。

4.1深拷贝和浅拷贝

在上述原型模式的实现中,实际上只是一个浅拷贝,这份拷贝实际上是副本的字段引用原始的字段引用。



A引用B就是说两个对象指向同一个地址,当A修改时,B也会改变,,B修改时,A同时也会改变。

此时修改Client中的代码如下:

public class Client {
public static void main(String[] args) {
//1.构建文档对象
WordDocument wordDocument = new WordDocument();

//2. 编辑文档、添加图片等
wordDocument.setmText("这是一篇文档");
wordDocument.addImage("图片1");
wordDocument.addImage("图片2");
wordDocument.addImage("图片3");
wordDocument.addImage("图片4");

//以原始对象为原型,拷贝一份副本
try {
WordDocument cloneWord = (WordDocument) wordDocument.clone();
cloneWord.showDocument();

//修改文档副本,不会影响原始文档
cloneWord.setmText("这是修改后的拷贝文档");
cloneWord.addImage("哈哈哈.jpg");
//此时原始文档已经被修改了,因为副本和原始文档指向同一个地址
wordDocument.showDocument();

} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}


原始文档和副本文档中的mImages指向同一个地址。

解决方式就是采用深度拷贝,即在拷贝对象时,对于引用型的字段也要采用拷贝的形式,而不是单纯引用的形式。

代码如下:

@Override
protected Object clone() throws CloneNotSupportedException {
try {
WordDocument wordDocument = (WordDocument) super.clone();
wordDocument.mText = this.mText;
//对mImages对象也调用clone函数,进行深拷贝
//            wordDocument.mImages = this.mImages;
wordDocument.mImages = (ArrayList<String>) this.mImages.clone();
return wordDocument;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}


在开发过程中,为了减少错误,,建议采用深拷贝模式,避免操作副本时影响原始对象的问题。

5. Android源码中的原型设计模式

ArrayList源码:

//元素数量
int size;
//实际存储数据的数组
transient Object[] array;
public Object clone() {
try {
ArrayList<?> result = (ArrayList<?>) super.clone();
result.array = array.clone
return result;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}


size是整型,并不是一个对象,它是值类型,并不是引用类型,所以不需要进行克隆。

在Android源码中的Intent

@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内部的clone方法,实际上并没有调用super.clone()方法来实现对象拷贝,而是调用了new Intent(this)。使用clone和new需要根据构造对象的成本来决定,如果对象的构造成本比较高或者构造比较麻烦,那么使用clone函数效率较高,否则可以使用new的形式。

6. Android开发中的原型设计模式

情景如下:模块A有修改信息的权利,模块B没有修改信息的权利,只有可读,并在另外一个包中。

UserInfo 用户信息类:

public class UserInfo implements Cloneable {
private String name;
private int age;

public UserInfo(String name, int age) {
this.name = name;
this.age = age;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

@Override
public String toString() {
return "UserInfo{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}

/**
* 覆写clone方法
*
* @return
* @throws CloneNotSupportedException
*/
@Override
protected Object clone() throws CloneNotSupportedException {
UserInfo userInfo = null;
try {
userInfo = (UserInfo) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return userInfo;
}
}


Session 间接修改用户信息类,setUserInfo()方法为包访问权限,所以只有当前包内的类可以访问。

public class Session implements Cloneable {
private UserInfo userInfo;

public UserInfo getUserInfo() {
try {
return (UserInfo) userInfo.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}

/**
* 包访问权限,表示只有当前包下面的类才可修改信息
*
* @param userInfo
*/
void setUserInfo(UserInfo userInfo) {
this.userInfo = userInfo;
}

@Override
public String toString() {
return "Session{" +
"userInfo=" + userInfo +
'}';
}
}


Client 测试类:

public class Client {
public static void main(String[] args) {
//模块A有设置信息的权利
UserInfo userInfo = new UserInfo("张三", 18);
Session session = new Session();
session.setUserInfo(userInfo);
System.out.println("before:" + session);

//模块B,另一个包中,另外一个人获取信息,但是不希望有修改信息的权利,只有可读。

session.getUserInfo().setName("李四");
System.out.println("after:" + session);

}
}


模块A和Session类处于同一个包下,当模块A设置信息后,处于包外的模块B通过getUserInfo获取信息后,拿到了当前UserInfo对象,所以为了防止信息被修改,在Session类的getUserInfo()方法中,返回了UserInfo的副本,达到了只有可读的效果。

7. 总结

使用原型模式可以解决构造复杂对象的资源消耗问题,能够在某些场景下提升创建对象的效率。

某个对象可能是只读的,为了防止外部对这个只读对象修改,通常可以通过返回一个对象拷贝的形式实现只读的限制。

优点:

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

缺点:

直接在内存中拷贝,构造函数是不会执行的。实际开发中应该注意这个问题。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息