|
| 1 | +Android设计模式源码解析之原型模式 |
| 2 | +==================================== |
| 3 | +> 本文为 [Android 设计模式源码解析](https://github.com/simple-android-framework-exchange/android_design_patterns_analysis) 中 原型模式 分析 |
| 4 | +> Android系统版本: 2.3 |
| 5 | +> 分析者:[Mr.Simple](https://github.com/bboyfeiyu),分析状态:未完成,校对者:[Mr.Simple](https://github.com/bboyfeiyu),校对状态:完成 |
| 6 | +
|
| 7 | +## 1. 模式介绍 |
| 8 | + |
| 9 | +### 模式的定义 |
| 10 | +用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。 |
| 11 | + |
| 12 | + |
| 13 | +### 模式的使用场景 |
| 14 | +1. 类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等,通过原型拷贝避免这些消耗; |
| 15 | +2. 通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式; |
| 16 | +3. 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用,即保护性拷贝。 |
| 17 | + |
| 18 | + |
| 19 | +## 2. UML类图 |
| 20 | + |
| 21 | + |
| 22 | +### 角色介绍 |
| 23 | +* Client : 客户端用户。 |
| 24 | +* Prototype : 抽象类或者接口,声明具备clone能力。 |
| 25 | +* ConcretePrototype : 具体的原型类. |
| 26 | + |
| 27 | + |
| 28 | +## 3. 模式的简单实现 |
| 29 | +### 简单实现的介绍 |
| 30 | + |
| 31 | +下面我们以简单的文档拷贝为例来演示一下简单的原型模式模式。 |
| 32 | + |
| 33 | +### 实现源码 |
| 34 | + |
| 35 | +```java |
| 36 | +package com.dp.example.builder; |
| 37 | + |
| 38 | + |
| 39 | +package com.dp.example.prototype; |
| 40 | + |
| 41 | +import java.util.ArrayList; |
| 42 | +import java.util.List; |
| 43 | + |
| 44 | +/** |
| 45 | + * 文档类型, 扮演的是ConcretePrototype角色,而cloneable是代表prototype角色 |
| 46 | + * |
| 47 | + * @author mrsimple |
| 48 | + */ |
| 49 | +public class WordDocument implements Cloneable { |
| 50 | + /** |
| 51 | + * 文本 |
| 52 | + */ |
| 53 | + private String mText; |
| 54 | + /** |
| 55 | + * 图片名列表 |
| 56 | + */ |
| 57 | + private ArrayList<String><string> mImages = new ArrayList<String><string>(); |
| 58 | + |
| 59 | + public WordDocument() { |
| 60 | + System.out.println("----------- WordDocument构造函数 -----------"); |
| 61 | + } |
| 62 | + |
| 63 | + /** |
| 64 | + * 克隆对象 |
| 65 | + */ |
| 66 | + @Override |
| 67 | + protected WordDocument clone() { |
| 68 | + try { |
| 69 | + WordDocument doc = (WordDocument) super.clone(); |
| 70 | + doc.mText = this.mText; |
| 71 | + doc.mImages = this.mImages; |
| 72 | + return doc; |
| 73 | + } catch (Exception e) { |
| 74 | + } |
| 75 | + |
| 76 | + return null; |
| 77 | + } |
| 78 | + |
| 79 | + public String getText() { |
| 80 | + return mText; |
| 81 | + } |
| 82 | + |
| 83 | + public void setText(String mText) { |
| 84 | + this.mText = mText; |
| 85 | + } |
| 86 | + |
| 87 | + public List<string> getImages() { |
| 88 | + return mImages; |
| 89 | + } |
| 90 | + |
| 91 | + /** |
| 92 | + * @param img |
| 93 | + */ |
| 94 | + public void addImage(String img) { |
| 95 | + this.mImages.add(img); |
| 96 | + } |
| 97 | + |
| 98 | + /** |
| 99 | + * 打印文档内容 |
| 100 | + */ |
| 101 | + public void showDocument() { |
| 102 | + System.out.println("----------- Word Content Start -----------"); |
| 103 | + System.out.println("Text : " + mText); |
| 104 | + System.out.println("Images List: "); |
| 105 | + for (String imgName : mImages) { |
| 106 | + System.out.println("image name : " + imgName); |
| 107 | + } |
| 108 | + System.out.println("----------- Word Content End -----------"); |
| 109 | + } |
| 110 | +} |
| 111 | +``` |
| 112 | + |
| 113 | +通过WordDocument类模拟了word文档中的基本元素,即文字和图片。WordDocument的在该原型模式示例中扮演的角色为ConcretePrototype, 而Cloneable的角色则为Prototype。WordDocument实现了clone方法以实现对象克隆。下面我们看看Client端的使用 : |
| 114 | + |
| 115 | +```java |
| 116 | +public class Client { |
| 117 | + public static void main(String[] args) { |
| 118 | + WordDocument originDoc = new WordDocument(); |
| 119 | + originDoc.setText("这是一篇文档"); |
| 120 | + originDoc.addImage("图片1"); |
| 121 | + originDoc.addImage("图片2"); |
| 122 | + originDoc.addImage("图片3"); |
| 123 | + originDoc.showDocument(); |
| 124 | + |
| 125 | + WordDocument doc2 = originDoc.clone(); |
| 126 | + doc2.showDocument(); |
| 127 | + |
| 128 | + doc2.setText("这是修改过的Doc2文本"); |
| 129 | + doc2.showDocument(); |
| 130 | + |
| 131 | + originDoc.showDocument(); |
| 132 | + } |
| 133 | + |
| 134 | +} |
| 135 | +``` |
| 136 | +输出结果如下 : |
| 137 | + |
| 138 | + |
| 139 | +可以看到,doc2是通过originDoc.clone()创建的,并且doc2第一次输出的时候和originDoc输出是一样的。即doc2是originDoc的一份拷贝,他们的内容是一样的,而doc2修改了文本内容以后并不会影响originDoc的文本内容。需要注意的是通过clone拷贝对象的时候并不会执行构造函数! |
| 140 | + |
| 141 | + |
| 142 | +### 浅拷贝和深拷贝 |
| 143 | +将main函数的内容修改为如下 : |
| 144 | + |
| 145 | +```java |
| 146 | + public static void main(String[] args) { |
| 147 | + WordDocument originDoc = new WordDocument(); |
| 148 | + originDoc.setText("这是一篇文档"); |
| 149 | + originDoc.addImage("图片1"); |
| 150 | + originDoc.addImage("图片2"); |
| 151 | + originDoc.addImage("图片3"); |
| 152 | + originDoc.showDocument(); |
| 153 | + |
| 154 | + WordDocument doc2 = originDoc.clone(); |
| 155 | + |
| 156 | + doc2.showDocument(); |
| 157 | + |
| 158 | + doc2.setText("这是修改过的Doc2文本"); |
| 159 | + doc2.addImage("哈哈.jpg"); |
| 160 | + doc2.showDocument(); |
| 161 | + |
| 162 | + originDoc.showDocument(); |
| 163 | + } |
| 164 | +``` |
| 165 | + |
| 166 | +输出结果如下 : |
| 167 | + |
| 168 | +细心的朋友可能发现了,在doc2添加了一张名为"哈哈.jpg"的照片,但是却也显示在originDoc中?这是怎么回事呢? 其实学习过C++的朋友都知道,这是因为上文中WordDocument的clone方法中只是简单的进行浅拷贝,引用类型的新对象doc2的mImages只是单纯的指向了this.mImages引用,而并没有进行拷贝。doc2的mImages添加了新的图片,实际上也就是往originDoc里添加了新的图片,所以originDoc里面也有"哈哈.jpg" 。那如何解决这个问题呢? 那就是采用深拷贝,即在拷贝对象时,对于引用型的字段也要采用拷贝的形式,而不是单纯引用的形式。示例如下 : |
| 169 | + |
| 170 | +```java |
| 171 | + /** |
| 172 | + * 克隆对象 |
| 173 | + */ |
| 174 | + @Override |
| 175 | + protected WordDocument clone() { |
| 176 | + try { |
| 177 | + WordDocument doc = (WordDocument) super.clone(); |
| 178 | + doc.mText = this.mText; |
| 179 | +// doc.mImages = this.mImages; |
| 180 | + doc.mImages = (ArrayList<String>) this.mImages.clone(); |
| 181 | + return doc; |
| 182 | + } catch (Exception e) { |
| 183 | + } |
| 184 | + |
| 185 | + return null; |
| 186 | + } |
| 187 | +``` |
| 188 | + |
| 189 | +如上代码所示,将doc.mImages指向this.mImages的一份拷贝, 而不是this.mImages本身,这样在doc2添加图片时并不会影响originDoc,如图所示 : |
| 190 | + |
| 191 | + |
| 192 | + |
| 193 | +## Android源码中的模式实现 |
| 194 | +在Android源码中,我们以熟悉的Intent来分析源码中的原型模式。简单示例如下 : |
| 195 | + |
| 196 | +```java |
| 197 | + Uri uri = Uri.parse("smsto:0800000123"); |
| 198 | + Intent shareIntent = new Intent(Intent.ACTION_SENDTO, uri); |
| 199 | + shareIntent.putExtra("sms_body", "The SMS text"); |
| 200 | + |
| 201 | + Intent intent = (Intent)shareIntent.clone() ; |
| 202 | + startActivity(intent); |
| 203 | +``` |
| 204 | + |
| 205 | +结果如下 : |
| 206 | + |
| 207 | + |
| 208 | + |
| 209 | +可以看到,我们通过shareIntent.clone方法拷贝了一个对象intent, 然后执行startActivity(intent), 随即就进入了短信页面,号码为0800000123,文本内容为The SMS text,即这些内容都与shareIntent一致。 |
| 210 | + |
| 211 | +```java |
| 212 | + @Override |
| 213 | + public Object clone() { |
| 214 | + return new Intent(this); |
| 215 | + } |
| 216 | + |
| 217 | + /** |
| 218 | + * Copy constructor. |
| 219 | + */ |
| 220 | + public Intent(Intent o) { |
| 221 | + this.mAction = o.mAction; |
| 222 | + this.mData = o.mData; |
| 223 | + this.mType = o.mType; |
| 224 | + this.mPackage = o.mPackage; |
| 225 | + this.mComponent = o.mComponent; |
| 226 | + this.mFlags = o.mFlags; |
| 227 | + if (o.mCategories != null) { |
| 228 | + this.mCategories = new ArraySet<String>(o.mCategories); |
| 229 | + } |
| 230 | + if (o.mExtras != null) { |
| 231 | + this.mExtras = new Bundle(o.mExtras); |
| 232 | + } |
| 233 | + if (o.mSourceBounds != null) { |
| 234 | + this.mSourceBounds = new Rect(o.mSourceBounds); |
| 235 | + } |
| 236 | + if (o.mSelector != null) { |
| 237 | + this.mSelector = new Intent(o.mSelector); |
| 238 | + } |
| 239 | + if (o.mClipData != null) { |
| 240 | + this.mClipData = new ClipData(o.mClipData); |
| 241 | + } |
| 242 | + } |
| 243 | +``` |
| 244 | + |
| 245 | + 可以看到,clone方法实际上在内部调用了new Intent(this); 这就和C++中的拷贝构造函数完全一致了,而且是深拷贝。由于该模式比较简单,就不做太多说明。 |
| 246 | + |
| 247 | + |
| 248 | +## 4. 杂谈 |
| 249 | +### 优点与缺点 |
| 250 | +* 优点 |
| 251 | +原型模式是在内存二进制流的拷贝,要比直接 new 一个对象性能好很多,特别是要在一个循环体内产生大量的对象时,原型模式可以更好地体现其优点。 |
| 252 | + |
| 253 | +* 缺点 |
| 254 | +这既是它的优点也是缺点,直接在内存中拷贝,构造函数是不会执行的,在实际开发当中应该注意这个潜在的问题。优点就是减少了约束,缺点也是减少了约束,需要大家在实际应用时考虑。 |
| 255 | + |
0 commit comments