案例:订单处理系统
订单分为个人订单和公司订单,客户对订单的要求如下:当订单的预定产品数量超过500时,需要将订单拆成两份订单来保存,如果拆分后的子订单数量依然超过500,那就继续拆分,直到每一份订单的数量都小于500为止。
鉴于此类问题,先用普通的处理方式进行解决
1.普通方法
定义接口
接口实现:PersonalOrder,EnterpriseOrder
PersonalOrder
EnterpriseOrder
订单管理类:OrderBusiness
客户端调用
如图所示,通过if,else的判断操作,我们也能实现这种需求,但是这样有个很明显的缺点,就是OrderBusiness太依赖于具体的订单的具体实现了,一来不满足最少知识的原则,二来不利于后续的扩展新的订单类型。 为了解决这些问题,我们引入原型模式来看下
2.原型模式
2.1 认识原型模式
定义: 用原型实例指定创建对象的种类,并通过拷贝这些原型对象创建新的对象。
- 要点:
1.通过克隆来创建新的对象实例
2.为克隆出来的新的对象实例复制原型实例属性的值
先来看一下通用的UML结构类图:
- client:使用原型的客户端,首先要获取到原型的实例对象,然后通过原型实例克隆自身来创建新的实例
- ProtoType:声明克隆自身的接口
- ConcreteProtoType:实现ProtoType定义的接口,实现克隆功能
此案例的UML结构图:
下面看下具体如何用原型模式改写案例:
- 定义具体原型类需要实现的接口
- 具体原型类,实现接口:
- 具体原型的管理类
- 客户端调用:和普通方法一样,不需要变动
说到原型模式,类似于拷贝,便需要有深拷贝(深度克隆),浅拷贝(浅度克隆)的区分,这块可以搜索深浅拷贝的区别自行理解,这里只简单描述下:
1.浅度克隆:只负责克隆按值传递的数据(如NSString,int等基本数据类型)
2.深度克隆:除了浅度克隆要克隆的值之外,还负责克隆引用类型的数据,也就是被克隆实例所有的属性数据都会被克隆出来
关于深度克隆克隆引用类型的数据我作一下简要说明: 比如有A类和B类,A中嵌套了B类,A类中有基础数据类型a,b,c,拥有的属性d是B类,关于这种嵌套引用形式的实例克隆
浅度克隆:克隆A类,生成一个新的实例对象C,a,b,c,d都是直接赋值给C,伪代码标识如下:
C.a = A.a
C.b = A.b
C.c = A.c
C.d = A.d
通俗点说就是浅度克隆对于引用类型的数据的克隆是直接将内存地址拷贝过去了
这里C中的d和A中的d指向同一个内存空间,如果操作了任意一个对象中的d,另一个也会跟这变
深度克隆:与浅度克隆差不多,区别就是对d的克隆,伪代码如下:
B *newD = [B new];
//将d中的所有属性值赋值给newD
newD.xxx = d.xxx
C.d = newD;
也就是说C中的d和A中的d已经不是同一块内存区域,而是两个独立的内存区域
关于深浅克隆可以对比深浅拷贝,也就是copy和strong的区别,这块自行补习理解,具体不再详细说明
- 注意:深度克隆中,如果克隆的对象一直有嵌套的引用类型,需要一直不断地递归克隆下去,也就是说要想克隆成功,必须要整个克隆所涉及的对象都要正确地实现克隆方法
最后来聊一聊原型模式的优缺点
优点
- 对客户端隐藏具体的实现类型
- 在运行时动态改变具体的实现类型
缺点
每个原型的子类都必须实现clone的操作,尤其是包含引用类型的对象时,克隆会比较麻烦