原型模式

前面讨论了工厂模式,这里接着探索创建型模式。在这里考虑一个订单系统,里面有一个保存订单的功能。要求是如果订单预定的产品数量超过1000,就需要把订单拆分成两份订单来保存。如果拆成两份后还是超过1000,那就继续拆分。知道每份订单数量不超过1000。至于为什么要拆分,是为了方便后续处理,后续是有人工来处理,每个人处理能力是1000。根据业务,目前订单类型有两种,企业订单和个人订单。

场景问题

分析上面的业务需求,有人可能会想,很简单嘛,一共就一个功能,没什么困难的,下面尝试实现一下。

首先,如果想要实现通用的订单处理,而不关心订单类型,订单处理对象应该面向一个订单的接口而不是具体对象。这里先定义订单接口:

@protocol OrderApi <NSObject>
// 订单数量
@property (nonatomic, assign) NSUInteger productNumber;
@end

个人订单实现:

@interface PersonalOrder : NSObject<OrderApi>
@property (nonatomic, copy) NSString *productId;
@property (nonatomic, copy) NSString *customerName;
@end
@implementation PersonalOrder
{
	NSUInteger _productNumber;
}

- (void)setProductNumber:(NSUInteger)productNumber {
	_productNumber = productNumber;
}
- (NSUInteger)productNumber {
	return _productNumber;
}
-(NSString *)description {
	return [NSString stringWithFormat:@"本个人订单的订购人是%@,订购产品是%@,订购数量是%zd",self.customerName,self.productId, self.productNumber];
}
@end

企业订单实现:

@interface EnterpriseOrder : NSObject<OrderApi>
@property (nonatomic, copy) NSString *productId;
@property (nonatomic, copy) NSString *enterpriseName;
@end
@implementation EnterpriseOrder
{
	NSUInteger _productNumber;
}

- (void)setProductNumber:(NSUInteger)productNumber {
	_productNumber = productNumber;
}
- (NSUInteger)productNumber {
	return _productNumber;
}
-(NSString *)description {
	return [NSString stringWithFormat:@"本个人订单的订购人是%@,订购产品是%@,订购数量是%zd",self.enterpriseName,self.productId, self.productNumber];
}
@end

实现好了订单对象,接下来看看如何实现通用订单处理:

@interface OrderBusiness : NSObject
- (void)saveOrder:(id<OrderApi>)order;
@end
@implementation OrderBusiness

- (void)saveOrder:(id<OrderApi>)order {
	while (order.productNumber > 1000) {
		// 在这里需要创建新的订单
	}
}
@end

写到这里有个问题,在订单商品数量超过1000的时候需要创建新的订单,但是这里只知道订单的接口,不知道具体是个人订单还是企业订单。有同学可能说在这里判断一下类型不就行了,但是这样就关心订单的类型和具体实现了。

现在这里有个问题:已经有了某种对象,如何能够快速简单的创建出更多的这种对象?

使用原型模式解决

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

分析上面的问题,已经有了订单接口类型的对象实例,是从外部传入的,但是这里并不知道对象的具体实现。但是现在需要创建一个相同类型的对象,看起来就像是通过接口来创建对象一样。

原型模式就是解决这样的问题,原型模式要求对象实现一个拷贝自身的接口,这样就可以通过拷贝方法来克隆一个实例对象本身来创建一个新的实例。如果把这个方法定义在接口上,看起来就像是通过接口来创建了新的对象。这样以来,通过原型实例创建新的对象实例,就不需要考虑对象的具体类型了,原型模式类图如下: 接下来用原型模式继续前面的例子:

@protocol OrderApi <NSObject>
@property (nonatomic, assign) NSUInteger productNumber;
// 接口加了这样一个方法
- (id<OrderApi>)copyOrder;
@end

// 个人订单
@interface PersonalOrder : NSObject<OrderApi>
@property (nonatomic, copy) NSString *productId;
@property (nonatomic, copy) NSString *customerName;
@end
@implementation PersonalOrder
{
	NSUInteger _productNumber;
}

- (void)setProductNumber:(NSUInteger)productNumber {
	_productNumber = productNumber;
}
- (NSUInteger)productNumber {
	return _productNumber;
}
-(NSString *)description {
	return [NSString stringWithFormat:@"本个人订单的订购人是%@,订购产品是%@,订购数量是%zd",self.customerName,self.productId, self.productNumber];
}
// 实现拷贝方法
- (id<OrderApi>)copyOrder {
	PersonalOrder *order = [[self class] new];
	order.productId = self.productId;
	order.productNumber = self.productNumber;
	order.customerName = self.customerName;
	return order;
}
@end

//企业订单
@interface EnterpriseOrder : NSObject<OrderApi>
@property (nonatomic, copy) NSString *productId;
@property (nonatomic, copy) NSString *enterpriseName;
@end
@implementation EnterpriseOrder
{
	NSUInteger _productNumber;
}

- (void)setProductNumber:(NSUInteger)productNumber {
	_productNumber = productNumber;
}
- (NSUInteger)productNumber {
	return _productNumber;
}
-(NSString *)description {
	return [NSString stringWithFormat:@"本个人订单的订购人是%@,订购产品是%@,订购数量是%zd",self.enterpriseName,self.productId, self.productNumber];
}
// 实现拷贝方法
- (id<OrderApi>)copyOrder {
	EnterpriseOrder *order = [[self class] new];
	order.productId = self.productId;
	order.productNumber = self.productNumber;
	order.enterpriseName = self.enterpriseName;
	return order;
}

// 处理订单业务
@interface OrderBusiness : NSObject
- (void)saveOrder:(id<OrderApi>)order;
@end
@implementation OrderBusiness
- (void)saveOrder:(id<OrderApi>)order {
	while (order.productNumber > 1000) {
		// 在这里直接拷贝
		id<OrderApi> newOrder = [order copyOrder];
		newOrder.productNumber  = 1000;
		order.productNumber  -= 1000;
		NSLog(@"拆分生成订单--%@", newOrder);
	}
	NSLog(@"订单--%@",order);
}
@end

// 客户端实现
int main(int argc, char * argv[]) {
	PersonalOrder *order = [PersonalOrder new];
	order.customerName = @"张三";
	order.productId = @"1";
	order.productNumber = 2005;
	
	OrderBusiness *business = [OrderBusiness new];
	[business saveOrder:order];
}
// 打印出来:
拆分生成订单--本个人订单的订购人是张三,订购产品是1,订购数量是1000
拆分生成订单--本个人订单的订购人是张三,订购产品是1,订购数量是1000
订单--本个人订单的订购人是张三,订购产品是1,订购数量是5

深拷贝和浅拷贝

说到原型模式,不可避免的需要谈一下深拷贝和浅拷贝。其实这两种很好区分。深拷贝是指对一个对象的内存进行一份拷贝,拷贝对象的内存和原对象的内存是不一样的,如果对象内部有引用类型,引用类型的内存也需要就行拷贝,一值拷贝下去。除了深拷贝,其他的都是浅拷贝,也就是说只拷贝一层引用也是浅拷贝。原型模式一般都是深拷贝。

思考原型模式

原型模式的本质是:拷贝生成对象。拷贝是手段,目标是生成新的对象实例。原型模式可以用来解决只知道接口而不知实现创建对象的问题。原型模式的重心是创建新的对象实例,至于创建出来的对象,其属性值和原型对象可以一样也可以不一样。目前大多数实现中是一样的,可以有选择性的拷贝。

原型是一个很容易理解的设计模式,在iOS中有NSCopy协议来让对象具有拷贝功能。

Freelf

iOS Developer

Beijing, China freelf.me