Android源码分析之原型模式

模式的定义

用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。


使用场景

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

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

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


UML类图



角色介绍

Client  :  客户端用户。

Prototype : 抽象类或者接口,声明具备clone能力。

ConcretePrototype : 具体的原型类.


简单示例

下面我们以简单的文档拷贝为例来演示一下简单的原型模式模式。

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. package com.dp.example.builder;  
  2.   
  3.   
  4. package com.dp.example.prototype;  
  5.   
  6. import java.util.ArrayList;  
  7. import java.util.List;  
  8.   
  9. /** 
  10.  * 文档类型, 扮演的是ConcretePrototype角色,而cloneable是代表prototype角色 
  11.  *  
  12.  * @author mrsimple 
  13.  */  
  14. public class WordDocument implements Cloneable {  
  15.     /** 
  16.      * 文本 
  17.      */  
  18.     private String mText;  
  19.     /** 
  20.      * 图片名列表 
  21.      */  
  22.     private ArrayList<String><string> mImages = new ArrayList<String><string>();  
  23.   
  24.     public WordDocument() {  
  25.         System.out.println("----------- WordDocument构造函数 -----------");  
  26.     }  
  27.   
  28.     /** 
  29.      * 克隆对象 
  30.      */  
  31.     @Override  
  32.     protected WordDocument clone() {  
  33.         try {  
  34.             WordDocument doc = (WordDocument) super.clone();  
  35.             doc.mText = this.mText;  
  36.             doc.mImages = this.mImages;  
  37.             return doc;  
  38.         } catch (Exception e) {  
  39.         }  
  40.   
  41.         return null;  
  42.     }  
  43.   
  44.     public String getText() {  
  45.         return mText;  
  46.     }  
  47.   
  48.     public void setText(String mText) {  
  49.         this.mText = mText;  
  50.     }  
  51.   
  52.     public List<string> getImages() {  
  53.         return mImages;  
  54.     }  
  55.   
  56.     /** 
  57.      * @param img 
  58.      */  
  59.     public void addImage(String img) {  
  60.         this.mImages.add(img);  
  61.     }  
  62.   
  63.     /** 
  64.      * 打印文档内容 
  65.      */  
  66.     public void showDocument() {  
  67.         System.out.println("----------- Word Content Start -----------");  
  68.         System.out.println("Text : " + mText);  
  69.         System.out.println("Images List: ");  
  70.         for (String imgName : mImages) {  
  71.             System.out.println("image name : " + imgName);  
  72.         }  
  73.         System.out.println("----------- Word Content End -----------");  
  74.     }  
  75. }  
  76. </string></string></string>  

通过WordDocument类模拟了word文档中的基本元素,即文字和图片。WordDocument的在该原型模式示例中扮演的角色为ConcretePrototype, 而Cloneable的角色则为Prototype。WordDocument实现了clone方法以实现对象克隆。下面我们看看Client端的使用 : 

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public class Client {  
  2.     public static void main(String[] args) {  
  3.         WordDocument originDoc = new WordDocument();  
  4.         originDoc.setText("这是一篇文档");  
  5.         originDoc.addImage("图片1");  
  6.         originDoc.addImage("图片2");  
  7.         originDoc.addImage("图片3");  
  8.         originDoc.showDocument();  
  9.   
  10.         WordDocument doc2 = originDoc.clone();  
  11.         doc2.showDocument();  
  12.           
  13.         doc2.setText("这是修改过的Doc2文本");  
  14.         doc2.showDocument();  
  15.            
  16.         originDoc.showDocument();  
  17.     }  
  18.   
  19. }  

输出结果如下 : 


可以看到,doc2是通过originDoc.clone()创建的,并且doc2第一次输出的时候和originDoc输出是一样的。即doc2是originDoc的一份拷贝,他们的内容是一样的,而doc2修改了文本内容以后并不会影响originDoc的文本内容。需要注意的是通过clone拷贝对象的时候并不会执行构造函数!

浅拷贝和深拷贝

将main函数的内容修改为如下 : 

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public static void main(String[] args) {  
  2.     WordDocument originDoc = new WordDocument();  
  3.     originDoc.setText("这是一篇文档");  
  4.     originDoc.addImage("图片1");  
  5.     originDoc.addImage("图片2");  
  6.     originDoc.addImage("图片3");  
  7.     originDoc.showDocument();  
  8.   
  9.     WordDocument doc2 = originDoc.clone();  
  10.       
  11.     doc2.showDocument();  
  12.       
  13.     doc2.setText("这是修改过的Doc2文本");  
  14.     doc2.addImage("哈哈.jpg");  
  15.     doc2.showDocument();  
  16.       
  17.     originDoc.showDocument();  
  18. }  

输出结果如下 : 


细心的朋友可能发现了,在doc2添加了一张名为"哈哈.jpg"的照片,但是却也显示在originDoc中?这是怎么回事呢?  其实学习过C++的朋友都知道,这是因为上文中WordDocument的clone方法中只是简单的进行浅拷贝,引用类型的新对象doc2的mImages只是单纯的指向了this.mImages引用,而并没有进行拷贝。doc2的mImages添加了新的图片,实际上也就是往originDoc里添加了新的图片,所以originDoc里面也有"哈哈.jpg" 。那如何解决这个问题呢?  那就是采用深拷贝,即在拷贝对象时,对于引用型的字段也要采用拷贝的形式,而不是单纯引用的形式。示例如下 : 

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1.     /** 
  2.      * 克隆对象 
  3.      */  
  4.     @Override  
  5.     protected WordDocument clone() {  
  6.         try {  
  7.             WordDocument doc = (WordDocument) super.clone();  
  8.             doc.mText = this.mText;  
  9. //            doc.mImages = this.mImages;  
  10.             doc.mImages = (ArrayList<String>) this.mImages.clone();  
  11.             return doc;  
  12.         } catch (Exception e) {  
  13.         }  
  14.   
  15.         return null;  
  16.     }  
如上代码所示,将doc.mImages指向this.mImages的一份拷贝, 而不是this.mImages本身,这样在doc2添加图片时并不会影响originDoc,如图所示 :



源码分析

在Android源码中,我们以熟悉的Intent来分析源码中的原型模式。简单示例如下 : 

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. Uri uri = Uri.parse("smsto:0800000123");      
  2.     Intent shareIntent = new Intent(Intent.ACTION_SENDTO, uri);      
  3.     shareIntent.putExtra("sms_body""The SMS text");      
  4.       
  5.     Intent intent = (Intent)shareIntent.clone() ;  
  6.     startActivity(intent);  

结果如下 : 


可以看到,我们通过shareIntent.clone方法拷贝了一个对象intent, 然后执行startActivity(intent), 随即就进入了短信页面,号码为0800000123,文本内容为The SMS text,即这些内容都与shareIntent一致。


下面我们看看Intent的clone的实现 : 

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. @Override  
  2. public Object clone() {  
  3.     return new Intent(this);  
  4. }  
  5.   
  6. /** 
  7.  * Copy constructor. 
  8.  */  
  9. public Intent(Intent o) {  
  10.     this.mAction = o.mAction;  
  11.     this.mData = o.mData;  
  12.     this.mType = o.mType;  
  13.     this.mPackage = o.mPackage;  
  14.     this.mComponent = o.mComponent;  
  15.     this.mFlags = o.mFlags;  
  16.     if (o.mCategories != null) {  
  17.         this.mCategories = new ArraySet<String>(o.mCategories);  
  18.     }  
  19.     if (o.mExtras != null) {  
  20.         this.mExtras = new Bundle(o.mExtras);  
  21.     }  
  22.     if (o.mSourceBounds != null) {  
  23.         this.mSourceBounds = new Rect(o.mSourceBounds);  
  24.     }  
  25.     if (o.mSelector != null) {  
  26.         this.mSelector = new Intent(o.mSelector);  
  27.     }  
  28.     if (o.mClipData != null) {  
  29.         this.mClipData = new ClipData(o.mClipData);  
  30.     }  
  31. }  
    可以看到,clone方法实际上在内部调用了new Intent(this); 这就和C++中的拷贝构造函数完全一致了,而且是深拷贝。由于该模式比较简单,就不做太多说明。


优点与缺点

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

缺点 :
1、这既是它的优点也是缺点,直接在内存中拷贝,构造函数是不会执行的,在实际开发当中应该注意这个潜在的问题。优点就是减少了约束,缺点也是减少了约束,需要大家在实际应用时考虑。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值