三、原型设计模式
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一个对象性能好很多,特别是要在一个循环体内产生大量的对象时,原型模式可以更好地体现其优点。缺点:
直接在内存中拷贝,构造函数是不会执行的。实际开发中应该注意这个问题。相关文章推荐
- java对世界各个时区(TimeZone)的通用转换处理方法(转载)
- java-注解annotation
- java-模拟tomcat服务器
- java-用HttpURLConnection发送Http请求.
- java-WEB中的监听器Lisener
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法