设计模式学习——原型模式

 

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

类型:创建类模式

类图:

原型模式主要用于对象的复制,它的核心是就是类图中的原型类Prototype。Prototype类需要具备以下两个条件:

  • 实现Cloneable接口。在java语言有一个Cloneable接口,它的作用只有一个,就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法。在java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出CloneNotSupportedException异常。
  • 重写Object类中的clone方法。Java中,所有类的父类都是Object类,Object类中有一个clone方法,作用是返回对象的一个拷贝,但是其作用域protected类型的,一般的类无法调用,因此,Prototype类需要将clone方法的作用域修改为public类型。

        原型模式是一种比较简单的模式,也非常容易理解,实现一个接口,重写一个方法即完成了原型模式。在实际应用中,原型模式很少单独出现。经常与其他模式混用,他的原型类Prototype也常用抽象类来替代。

实现代码:

  1. class Prototype implements Cloneable {  
  2.     public Prototype clone(){  
  3.         Prototype prototype = null;  
  4.         try{  
  5.             prototype = (Prototype)super.clone();  
  6.         }catch(CloneNotSupportedException e){  
  7.             e.printStackTrace();  
  8.         }  
  9.         return prototype;   
  10.     }  
  11. }  
  12.   
  13. class ConcretePrototype extends Prototype{  
  14.     public void show(){  
  15.         System.out.println("原型模式实现类");  
  16.     }  
  17. }  
  18.   
  19. public class Client {  
  20.     public static void main(String[] args){  
  21.         ConcretePrototype cp = new ConcretePrototype();  
  22.         for(int i=0; i< 10; i++){  
  23.             ConcretePrototype clonecp = (ConcretePrototype)cp.clone();  
  24.             clonecp.show();  
  25.         }  
  26.     }  
  27. }  

原型模式的优点及适用场景

       使用原型模式创建对象比直接new一个对象在性能上要好的多,因为Object类的clone方法是一个本地方法,它直接操作内存中的二进制流,特别是复制大对象时,性能的差别非常明显。

       使用原型模式的另一个好处是简化对象的创建,使得创建对象就像我们在编辑文档时的复制粘贴一样简单。

       因为以上优点,所以在需要重复地创建相似对象时可以考虑使用原型模式。比如需要在一个循环体内创建对象,假如对象创建过程比较复杂或者循环次数很多的话,使用原型模式不但可以简化创建过程,而且可以使系统的整体性能提高很多。

原型模式的注意事项

  • 使用原型模式复制对象不会调用类的构造方法。因为对象的复制是通过调用Object类的clone方法来完成的,它直接在内存中复制数据,因此不会调用到类的构造方法。不但构造方法中的代码不会执行,甚至连访问权限都对原型模式无效。还记得单例模式吗?单例模式中,只要将构造方法的访问权限设置为private型,就可以实现单例。但是clone方法直接无视构造方法的权限,所以,单例模式与原型模式是冲突的,在使用时要特别注意。
  • 深拷贝与浅拷贝。Object类的clone方法只会拷贝对象中的基本的数据类型,对于数组、容器对象、引用对象等都不会拷贝,这就是浅拷贝。如果要实现深拷贝,必须将原型模式中的数组、容器对象、引用对象等另行拷贝。例如:
  1. public class Prototype implements Cloneable {  
  2.     private ArrayList list = new ArrayList();  
  3.     public Prototype clone(){  
  4.         Prototype prototype = null;  
  5.         try{  
  6.             prototype = (Prototype)super.clone();  
  7.             prototype.list = (ArrayList) this.list.clone();  
  8.         }catch(CloneNotSupportedException e){  
  9.             e.printStackTrace();  
  10.         }  
  11.         return prototype;   
  12.     }  
  13. }  

        由于ArrayList不是基本类型,所以成员变量list,不会被拷贝,需要我们自己实现深拷贝,幸运的是java提供的大部分的容器类都实现了Cloneable接口。所以实现深拷贝并不是特别困难。

PS:深拷贝与浅拷贝问题中,会发生深拷贝的有java中的8中基本类型以及他们的封装类型,另外还有String类型。其余的都是浅拷贝。


浅复制与深复制


非指针型实例变量没有浅复制与深复制之分,像布尔型、整型、浮点型。浅复制与深复制是针对指针型实例变量说的,浅复制就只是复制了指针到副本中,原始对象与副本共享内存数据;深复制就是把内存的资源也复制了,原始对象和副本分别对应自己的内存数据。下面的示意图左边为浅复制,右边为深复制。Cocoa Touch框架为NSObject的派生类提供了复制协议即NSCopying协议,NSObject子类需要实现其方法(id)copyWithZone:(NSZone *)zone。NSObject有一个实例方法(id)copy,这个方法默认调用了[self copyWithZone:nil],当然在里面你可以实现浅层复制,也可以实现深层复制。


Prototype类中包括一个Clone方法,Client知道Prototype,调用其复制方法clone即可得到多个实例,不需要手工去创建这些实例。ConcretePrototype为Prototype的子类,实现自身的clone方法,如果Client通过调用ConcretePrototype的clone方法,将返回ConcretePrototype的实例。


例子


新建Prototype类,Prototype.h如下:

[cpp]  view plain copy
  1. #import <Foundation/Foundation.h>  
  2.   
  3. //Prototype使用了NSCopying协议,里面只有一个方法:- (id)copyWithZone:(NSZone *)zone;  
  4. @interface Prototype : NSObject<NSCopying>  
  5.   
  6. //设置一个属性,用来检测复制的变化  
  7. @property(nonatomic, strong) NSString *name;  
  8.   
  9. @end  


实现深复制,Prototype.m文件如下:


[cpp]  view plain copy
  1. #import "Prototype.h"  
  2.   
  3. @implementation Prototype  
  4.   
  5. - (id)init  
  6. {  
  7.     if (self = [super init])  
  8.     {  
  9.         //初始化Prototype类时,将name设置如下  
  10.         self.name = @"My name is Prototype";  
  11.     }  
  12.   
  13.     return self;  
  14. }  
  15.   
  16. //实现NSCopying中的方法  
  17. - (id)copyWithZone:(NSZone *)zone  
  18. {  
  19.     //调用allocWithZone方法,复制一个新对象  
  20.     return [[[self class] allocWithZone:zone] init];  
  21. }  
  22.   
  23. @end  



测试代码如下:

[cpp]  view plain copy
  1. //创建Prototype实例 prototype  
  2. Prototype *prototype = [[Prototype alloc] init];  
  3. //输出prototype  
  4. NSLog(@"prototype:%@, name:%@", prototype, prototype.name);  
  5. //通过prototype深复制出一个新的对象prototypeCopy  
  6. Prototype *prototypeDeepCopy = [prototype copy];  
  7. //通过prototype直接赋值,其实就是复制了指针,属于浅复制,引用计数加1  
  8. Prototype *prototypeShallowCopy = prototype;  
  9. NSLog(@"prototypeShallowCopy:%@, name:%@", prototypeShallowCopy, prototypeShallowCopy.name);  
  10. //修改prototype  
  11. prototype.name = @"My name is new Prototype";  
  12. //输出prototypeDeepCopy  
  13. NSLog(@"prototype:%@, name:%@", prototype, prototype.name);  
  14. NSLog(@"prototypeDeepCopy:%@, name:%@", prototypeDeepCopy, prototypeDeepCopy.name);  
  15. NSLog(@"prototypeShallowCopy:%@, name:%@", prototypeShallowCopy, prototypeShallowCopy.name);  


输出结果如下(省略时间及项目名):

[cpp]  view plain copy
  1. prototype:<Prototype: 0x71330d0>, name:My name is Prototype  
  2. prototypeShallowCopy:<Prototype: 0x71330d0>, name:My name is Prototype  
  3. prototype:<Prototype: 0x71330d0>, name:My name is new Prototype  
  4. prototypeDeepCopy:<Prototype: 0xc6138d0>, name:My name is Prototype  
  5. prototypeShallowCopy:<Prototype: 0x71330d0>, name:My name is new Prototype  


结论


1、我们在copyWithZone:(NSZone *)zone实现了深复制,通过prototype复制得到prototypeDeepCopy,从输出结果来看,内存地址与prototype是不一样的,另外复制得到prototypeCopy后修改了prototype的name,对prototypeCopy的name值没有影响,可判断为深复制;


2、使用直接赋值得到的prototypeShallowCopy,内存地址与prototype一样,只是简单的指针复制,另外从修改了prototype的name值同时也影响了prototypeShallowCopy的name值也可以看出,这种为浅复制。


题外话

像NSString、NSDictionary这些类,本身已经实现了copyWithZone:(NSZone *)zone方法,直接使用如[NSString copy]调用即可。在复制后得到的副本,又可以分为可变副本(mutable copy)和不可变副本(immutable copy)。通常在NSCopying协议规定的方法copyWithZone中返回不可变副本,在NSMutableCopying的mutableCopyWithZone方法中返回可变副本,然后调用copy和mutableCopy方法来得到副本。


NSDictionary类已经遵循NSCopying协议及NSMutableCopying协议,下面进行测试。


第一种情况:

[cpp]  view plain copy
  1. NSDictionary *srcDic = [NSDictionary dictionaryWithObjectsAndKeys:@"value", @"key", nil];  
  2. NSDictionary *dicCopy = [srcDic copy];//注意这里用了copy  
  3. NSLog(@"srcDic:%p, %@", srcDic, srcDic);  
  4. NSLog(@"dicCopy:%p %@", dicCopy, dicCopy);  


输出结果:


[cpp]  view plain copy
  1. srcDic:0x8a73850, {  
  2.     key = value;  
  3. }  
  4. dicCopy:0x8a73850 {  
  5.     key = value;  
  6. }  


结论:


由[srcDic copy]得到的dicCopy,两者内存地址一致,由于copy返回的是不可变副本,系统只生成一份内存资源,此时的copy只是浅复制,retain作用一样。

第二种情况:


[cpp]  view plain copy
  1. NSDictionary *srcDic = [NSDictionary dictionaryWithObjectsAndKeys:@"value", @"key", nil];  
  2. NSDictionary *dicCopy = [srcDic mutableCopy];//注意这里使用了mutableCopy  
  3. NSLog(@"srcDic:%p, %@", srcDic, srcDic);  
  4. NSLog(@"dicCopy:%p %@", dicCopy, dicCopy);  


输出结果:


[cpp]  view plain copy
  1. srcDic:0x8938c50, {  
  2.     key = value;  
  3. }  
  4. dicCopy:0x8938b60 {  
  5.     key = value;  
  6. }  



结论:


由[srcDic mutableCopy]得到的dicCopy,两者内存地址不一致,由于mutableCopy返回的是可变副本,系统生成了新的内存资源,此时的mutableCopy是深复制。疑问:那么如果srcDic是NSMutableDictionary类型而使用copy,又会怎么样呢?且看第三种情况。

第三种情况:


[cpp]  view plain copy
  1. NSMutableDictionary *srcDic = [NSMutableDictionary dictionaryWithObjectsAndKeys:@"value", @"key", nil];  
  2. NSMutableDictionary *dicCopy = [srcDic copy];//注意这里对NSMutableDictionary进行copy操作  
  3. NSLog(@"srcDic:%p, %@", srcDic, srcDic);  
  4. NSLog(@"dicCopy:%p %@", dicCopy, dicCopy);  
  5. [dicCopy setObject:@"newValue" forKey:@"key"];//尝试修改dicCopy里面的值  


输出情况:


[cpp]  view plain copy
  1. srcDic:0x7144ea0, {  
  2.     key = value;  
  3. }  
  4. dicCopy:0x71445c0 {  
  5.     key = value;  
  6. }  
  7. [__NSDictionaryI setObject:forKey:]: unrecognized selector sent to instance 0x71445c0  


结论:

由[srcDic mutableCopy]得到的dicCopy,两者内存地址不一致,即是copy对NSMutableDictionary类型进行了深复制,当尝试修改dicCopy里面的值时,发现报错了,修改不了,可以确定副本dicCopy是不可变副本。

总的结论


对于系统中已经实现的同时支持NSCopying协议和NSMutableCopying协议的,copy总是返回不可变副本,mutableCopy总是返回可变副本。




相关文章
相关标签/搜索