对象工厂中心是我生造的一个词,指的是整个程序中,一类对象由唯一的对象工厂创建。
我们用一个实际的场景来说明。假设要实现一个IM模块,为App提供即时通讯支持。我们需要定义消息数据,并处理消息的发送和接收。在Objective-C的世界中,可以定义消息基类,然后定义一组子类表示不同类型的消息,比如文字,语音,URL链接消息等。而处理网络通信时,需要将对象数据序列化,就不得不用一个type字段来区别不同的消息类型。
序列化很简单,在基类中定义抽象序列化方法,子类中重写它,输出相应的序列化字符串。但在序列化过程中,消息对象的多态性丢失了,反序列化就需要一个对象工厂,依据type字段来确定需要创建的消息对象的类型。
最简单的工厂实现,就是一串if-else:
if ([type isEqualToString:@"typeA"]){
return [[ModelA alloc] init];
}else if ([type isEqualToString:@"typeB"]){
return [[ModelB alloc] init];
}
...
问题是,每增加一种消息类型,就需要修改工厂的实现,增加一个if case。这不符合对扩展开放,对修改关闭的原则。而且工厂类需要依赖所有的数据子类。
不同的消息可能由不同的模块处理。如果某个业务模块需要增加一种消息类型,我们当然不希望这种业务逻辑入侵到下层的IM模块,最好能让业务模块管理自己的消息子类,IM模块在不知道消息子类名字的情况下正确创建消息对象,返回给上层的业务模块。而对象初始化必须知道对象的类型,怎么办呢?
我们可以利用Objective-C的动态特性。在Objective-C中,对象的类型由Class对象来描述,可以给Class发送alloc消息来创建对应的对象。而且利用Objective-C runtime,Class和NSString可以相互转化。
在工厂类内部维护一个字典,保存type字段和对应的类型名称字符串,并对外开放一个注册Class的接口。上层业务模块将自己的子类注册到到工厂类,这样就能够通过type字段得到对应的Class,进而正确地进行对象初始化。
尝试实现一下。定义MessageBase作为消息基类,以及默认的TextMessage。定义MessageFactory作为消息工厂。
Message.h
#import <Foundation/Foundation.h>
static NSString * const MessageTypeText = @"text";
@interface MessageBase : NSObject
@property(nonatomic, strong, readonly)NSString *messageType;
- (instancetype)initWithJsonDict:(NSDictionary *)jsonDict;
@end
@interface TextMessage : MessageBase
@property (nonatomic, strong, readonly)NSString *content;
@end
MessageFactory.h
#import <Foundation/Foundation.h>
#import "Message.h"
@interface MessageFactory : NSObject
+ (void)registerMessageClass:(Class)messageClass forType:(NSString *)messageType;
+ (__kindof MessageBase *)messageByJsonDict:(NSDictionary *)jsonDict;
@end
MessageFactory.m
#import "MessageFactory.h"
#import "Message.h"
static NSString * const MessageTypeKey = @"message_type";
static NSMutableDictionary<NSString *, NSString *>* messageTypeDict;
@implementation MessageFactory
+ (void)registerMessageClass:(Class)messageClass forType:(NSString *)messageType
{
if (!messageTypeDict) {
messageTypeDict = [[NSMutableDictionary alloc] init];
}
NSString *className = NSStringFromClass(messageClass);
[messageTypeDict setObject:className forKey:messageType];
NSLog(@"register class %@ for type %@", className, messageType);
}
+ (__kindof MessageBase *)messageByJsonDict:(NSDictionary *)jsonDict
{
NSString *type = jsonDict[MessageTypeKey];
if (!type || ![type isKindOfClass:[NSString class]]) {
return nil;
}
NSString *className = messageTypeDict[type];
if (!className) {
return nil;
}
MessageBase *message = [(MessageBase *)[NSClassFromString(className) alloc] initWithJsonDict:jsonDict];
return message;
}
@end
现在,我们定义新的message类型后,只需要在程序开始时调用registerMessageClass:forType:
接口注册,MessageFactory就能用相应的字典数据初始化它了。
但调用注册接口还是有些麻烦,也可能会忘记调用。好在NSObject有一个很方便的+(void)load
方法。它会在一个类被加载到runtime时调用,父类的方法先被调用,然后子类和cateogory的方法被调用。
我们可以在load
方法中完成对象类型的注册。如果需要支持新的类型,只需要给MessageFactory
写一个category,实现自己的load
方法,在里面注册新的类型即可。比如我们要默认注册TextMessage
,并增加对超链接消息LinkMessage
的支持:
MessageFactory.m
+ (void)load
{
if (!messageTypeDict) {
messageTypeDict = [[NSMutableDictionary alloc] init];
}
[self registerMessageClass:[TextMessage class] forType:MessageTypeText];
}
MessageFactory+LinkMessage.m
#import "MessageFactory+LinkMessage.h"
#import "LinkMessage.h"
@implementation MessageFactory (LinkMessage)
+ (void)load
{
[self registerMessageClass:[LinkMessage class] forType:MessageTypeLink];
}
@end
我们在registerMessageClass:forType:
方法、main函数和application: didFinishLaunchingWithOptions:
中加了log,可以在程序运行时看到类型注册发生的时间:
2016-11-28 00:29:53.718 FactoryDemo[49999:7451402] register class TextMessage for type text
2016-11-28 00:29:53.723 FactoryDemo[49999:7451402] register class LinkMessage for type link
2016-11-28 00:29:53.723 FactoryDemo[49999:7451402] main called
2016-11-28 00:29:53.846 FactoryDemo[49999:7451402] application didFinishLaunchingWithOptions
可以看到load
调用发生在程序启动之前。
简单测试一下:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSString *textMessageJson = @"{\"message_type\":\"text\",\"content\":\"hello\"}";
NSString *linkMessageJson = @"{\"message_type\":\"link\",\"link\":\"http://www.baidu.com\"}";
NSDictionary *textMessageDict = [NSJSONSerialization JSONObjectWithData:[textMessageJson dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];
NSDictionary *linkMessageDict = [NSJSONSerialization JSONObjectWithData:[linkMessageJson dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];
TextMessage *textMessage = [MessageFactory messageByJsonDict:textMessageDict];
NSLog(@"text message content:%@",textMessage.content);
LinkMessage *linkMessage = [MessageFactory messageByJsonDict:linkMessageDict];
NSLog(@"link message url:%@",linkMessage.linkUrl);
}
如我们所料,成功创建了TextMessage
和LinkMessage
对象:
2016-11-28 00:29:53.852 FactoryDemo[49999:7451402] text message content:hello
2016-11-28 00:29:53.852 FactoryDemo[49999:7451402] link message url:http://www.baidu.com
如果……Swift?
在Swift中虽然也能继承NSObject来使用Objective-C的runtime,但这毕竟不是Swift style。
利用Swift的闭包特性,也可以实现可扩展的对象工厂中心。
首先定义一个无参数,返回MessageBase的闭包类型:
typealias MessageCreator = () -> MessageBase
在MessageFactory
中用字典记录type字段和对应的MessageCreator
闭包,客户模块将消息子类的创建写在闭包中,注册给MessageFactory
即可。收到消息时,MessageFactory
根据type字段取出闭包,然后闭着眼睛调用即可生成相应的消息对象。具体的实现就留给读者自己去完成吧。
完整的demo代码可以从我的Github下载。