摘要:
1.本文将详细介绍原型模式的原理和实际代码中特别是Android系统代码中的应用。
纲要:
1. 引入原型模式
2. 原型模式的概念及优缺点介绍
3. 原型模式对拷贝的使用
4. 原型模式在Android源码中的应用
1.先来一个段子:
GG和MM经常在QQ上聊天,但是GG打字的速度慢如蜗牛爬行,每次MM在瞬间完成恢复或者问候是,GG都会很紧张的去尽力快速打字,尽管如此,还是让MM有些不高兴,MM说回复信息这么慢,显然是用心不专,不在乎她。哎,GG也是百口难辩啊,不过也确实是没有办法。
有一天,GG想自己的密友K倾诉了自己的苦衷。K顿生大笑。说道:“傻瓜,你怎么不去网上收集一些肉麻的情话以及一些你们经常说话会涉及到主题,把这些东西拷贝下来保存在自己的电脑或者U盘里面,这样一来如果下次在聊天就可以借用过来了!”,“K就是K,我怎么没有想到呢~妙极~妙极^_^”,“不过不要太高兴,这些东西是要适当修改的,要不然你把名字都搞错的话,就等着你的MM把你踹死吧O(∩_∩)O哈哈~”K补充道,“嗯,说的对,谢谢K哥解决了我的心腹之患啊”GG乐不可支的说道。
这是MM由在网上和GG聊天,GG专门复制那些实现准备好的肉麻情话经过稍加修改后发给MM,MM都快美死了…(摘自Android之大话设计模式)
2.原型模式介绍
2.1什么是原型模式?
在类A创建实例的时候,会将传入的类B的实例(这个实例被称为原型对象)进行克隆,以此快速的获得与传入对象一致的初始化数据。克隆是原型模式最显著特点。
类A被称为隔离类,因为原型模式将类A隔离起来,不需要关注类B的变化。
类B称为原型类,实现了clone()接口给类A克隆。类B是易变的,但是实现的接口不易变。类B通常是一个抽象类,传入给类A的实例是类B的子类的实例。
2.2这样做有什么好处?
1、 方便。如果一个原型对象本身数据非常多,一个一个赋值是不现实的。通过进行克隆,我们可以快速的获得这个原型对象的全部数据。
2、 耦合度低。如果原型对象是个“易变类”,即这个类可能会有各种各样的继承,或者这个类随着开发的进行可能会不断的修改。但只要类B实现的接口是稳定不易变的, 原型模式可以使隔离类保持接口的稳定性,不需要随着类的变化而变化。
3.1关于拷贝:
克隆有两种,浅拷贝和深拷贝。浅拷贝就是只克隆一次,对被克隆的类的所有属性进行直接的赋值,而不是递归的克隆。递归的克隆就是对自己的属性也调用它自己的克隆方法。对于基本类型(七种基本类型boolean,char,byte,short,float,double.long)来说,赋值将得到此基本类型的一份拷贝。如果是非基本类型,则得到引用的拷贝。引用的拷贝意味着什么?意味着直接赋值之后,这两个普通类的实例的指向的数据空间是同一个。也就是说它们的修改会互相影响。我们举一个例子:
两个类A,B,它们的属性如下:
Class A { int a1; String a2; B b; } Class B { int b1; String b2; }
如果我们要实现Class A的浅拷贝,它的clone方法将会是这样的:
Object clone() { return super.clone(); }
万类之王Object自己已经实现了clone方法,我们只需要调用就可以了,在这里Object.clone()其实等价于:
A aa = new A(); aa.a1 = this.a1; aa.a2 = this.a2; aa.b = this.b; return a;
当然,这只是等价,不是真正的实现方法,毕竟Object不知道这个Class A有什么属性。万类之王是通过字节拷贝实现的,调用的是native方法。Class B是非基本类型,所以直接赋值只会得到b引用的拷贝,新克隆出来的aa实例和原实例a指向了同一个b实例的数据空间。所以对a.b的改动会影响到原实例a的b改动。
如果是深拷贝,我们应该这样实现:
Object clone() { A aa = (A)super.clone(); aa.b = (B)a.b.clone(); return (Object)aa; }
其实就是把非基本类型的属性再进行一次克隆就可以了,如果该属性中仍有非基本类型,则需要同样实现深拷贝。这里也就暴露出原型模式的缺点:实现原型模式的类的属性需要全部实现clone(),否则无法实现该模式。对于一个新创建的类来说也许不难,但如果中后期加入,则会需要大量的改动。
因为Java故意的隐瞒了指针这个概念,导致这里的理解有点复杂。从C语言的角度来看,int,float等类型为基本类型,他们本身在声明的时候就拥有了自己储存值的空间,赋值的时候是把值重新拷贝了一份。而自己定义的struct结构体,我们声明的是对应的struct*指针,不同指针指向同一个内存块,它们的修改会互相影响。
3.2Interger,String等包装类的拷贝问题:
细心的同学可能发现了,String不是基础类型,为什么在上面直接赋值就可以了?String的存储实际上通过char[]来实现的,同样,Interger是int的包装类。包装类的特质之一就是在对其值进行操作时会体现出其对应的基本类型的性质。关于该问题的详细情况可以访问:http://freej.blog.51cto.com/235241/168676。
3.3原型模式使用的是哪种拷贝呢?
设计模式里并没有指定原型模式是采用哪种拷贝,我们应根据实际情况选择深拷贝和浅拷贝。当然深拷贝应用的场景要多很多。此外,拷贝也没有指定需要拷贝所有属性,也是根据实际情况选择就可以了。
4.Android源码实战举例:
例子:Intent
Intent是一个实现了简单的原型模式的类。它的clone是这样的:
@Override public Object clone() { return new Intent(this); } 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; //下面几个是引用对象被重新创建了,是深拷贝 if (o.mCategories != null) { this.mCategories = new HashSet<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是一个深拷贝。有趣的是,它的属性也是使用构造器这种方式进行拷贝。在这种情况下,上文提到的“隔离类”和“易变类”都是自己。
new Intent(Intent o)这种构造方法在源码中屡见不鲜。如Launcher3中的LauncherModel.java,frameworks中的LauncherActivity.java,Settings中的DockEventReceiver。我们可以通过搜索“Intent(intent”或“Intent(mIntent”看到。
版权所有,转载请注明出处: