建造者模式
Builder模式,中文翻译为建造者模式或者构建者模式,也叫做生成器模式。
为什么需要建造者模式?
需要定义一个资源池配置类DMResourcePoolConfig。这里的资源池,可以理解为线程池、连接池、对象池等。在这个资源池配置类中,有以下几个成员变量,也就是可配置项。
builder01.png因为maxTotal、maxIdle、minIdle不是必填变量,所以在创建DMResourcePoolConfig对象的时候,通过init方法,给这几个参数传递nil,来表示使用默认值。
@interface DMResourcePoolConfig : NSObject
@property (nonatomic, strong, readonly) NSString *name;
@property (nonatomic, assign, readonly) NSInteger maxTotal;
@property (nonatomic, assign, readonly) NSInteger maxIdle;
@property (nonatomic, assign, readonly) NSInteger minIdle;
@end
@interface DMResourcePoolConfig ()
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger maxTotal;
@property (nonatomic, assign) NSInteger maxIdle;
@property (nonatomic, assign) NSInteger minIdle;
@end
@implementation DMResourcePoolConfig
- (instancetype)initWithName:(NSString *)name maxTotal:(NSNumber *)maxTotal maxIdle:(NSNumber *)maxIdle minIdle:(NSNumber *)minIdle
{
if (self = [super init]) {
if (name.length == 0) {
@throw [NSException exceptionWithName:@"DMResourcePoolConfigException" reason:@"name 不能为空" userInfo:nil];
}
self.name = name;
if (maxTotal) {
if ([maxTotal integerValue] < 0) {
@throw [NSException exceptionWithName:@"DMResourcePoolConfigException" reason:@"maxTotal 不能为负数" userInfo:nil];
} else {
self.maxTotal = [maxTotal integerValue];
}
} else {
self.maxTotal = 8;
}
if (maxIdle) {
if ([maxIdle integerValue] < 0) {
@throw [NSException exceptionWithName:@"DMResourcePoolConfigException" reason:@"maxIdle 不能为负数" userInfo:nil];
} else {
self.maxIdle = [maxIdle integerValue];
}
} else {
self.maxIdle = 8;
}
if (minIdle) {
if ([minIdle integerValue] < 0) {
@throw [NSException exceptionWithName:@"DMResourcePoolConfigException" reason:@"minIdle 不能为负数" userInfo:nil];
} else {
self.minIdle = [minIdle integerValue];
}
} else {
self.minIdle = 0;
}
}
return self;
}
@end
现在DMResourcePoolConfig 只有4个可配置项,对应的init函数中,也只有4个参数,参数的个数不多。但是如果可配置项逐渐增多,变成了8个、10个,甚至更多,继续沿用现在的设计思路,init函数的参数列表会变得很长,代码在可读性和易用性上都会变差。解决这个问题的办法就是用setter方法来成成员变量赋值。配置项name是必填的,把它放到init函数中设置,其它配置项都不是必填的,用setter方法设置。
@interface DMResourcePoolConfig : NSObject
@property (nonatomic, strong, readonly) NSString *name;
@property (nonatomic, assign) NSInteger maxTotal;
@property (nonatomic, assign) NSInteger maxIdle;
@property (nonatomic, assign) NSInteger minIdle;
@end
@interface DMResourcePoolConfig ()
@property (nonatomic, strong) NSString *name;
@end
@implementation DMResourcePoolConfig
- (instancetype)initWithName:(NSString *)name
{
if (self = [super init]) {
if (name.length == 0) {
@throw [NSException exceptionWithName:@"DMResourcePoolConfigException" reason:@"name 不能为空" userInfo:nil];
}
self.name = name;
}
return self;
}
- (void)setMaxTotal:(NSInteger)maxTotal
{
if (maxTotal < 0) {
@throw [NSException exceptionWithName:@"DMResourcePoolConfigException" reason:@"maxTotal 不能为负数" userInfo:nil];
} else {
_maxTotal = maxTotal;
}
}
- (void)setMaxIdle:(NSInteger)maxIdle
{
if (maxIdle < 0) {
@throw [NSException exceptionWithName:@"DMResourcePoolConfigException" reason:@"maxIdle 不能为负数" userInfo:nil];
} else {
_maxIdle = maxIdle;
}
}
- (void)setMinIdle:(NSInteger)minIdle
{
if (minIdle < 0) {
@throw [NSException exceptionWithName:@"DMResourcePoolConfigException" reason:@"minIdle 不能为负数" userInfo:nil];
} else {
_minIdle = minIdle;
}
}
@end
通过init方法设置必填项,通过setter方法设置可选配置项。如果还要解决下面三个问题,现在的设计思路就不满足了。
- name是必填项,如果必填的配置项有很多,把这些必填配置项都放到init方法中设置,那init方法又会出现参数列表很长的问题。如果把必填项也通过setter方法设置,那校验这些必填项是否已经填写的逻辑就无处安放了。
- 假设配置项之间有一定的依赖关系。比如,maxIdle和minIdle要小于等于maxTotal。继续使用现在的设计思路,这些配置项之间的依赖关系的校验逻辑就无处安放了。
- 如果希望DMResourcePoolConfig类对象是不可变对象,也就是,对象在创建好之后,就不能修改内部属性值。要实现这个功能,就不能在DMResourcePoolConfig类中暴露setter方法。
为了解决这些问题,建造者模式就派上用场了。可以把校验逻辑放置到Builder类中,先创建建造者,并通过setter方法设置建造者的变量值,然后使用build方法真正创建对象之前,做集中的校验,校验通过之后才会创建对象。
@interface DMResourcePoolConfigBuilder : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger maxTotal;
@property (nonatomic, assign) NSInteger maxIdle;
@property (nonatomic, assign) NSInteger minIdle;
- (DMResourcePoolConfig *)build;
@end
@implementation DMResourcePoolConfigBuilder
- (instancetype)init
{
if (self = [super init]) {
self.maxTotal = 8;
self.maxIdle = 8;
self.minIdle = 0;
}
return self;
}
- (void)setName:(NSString *)name
{
if (name == nil || name.length == 0) {
@throw [NSException exceptionWithName:@"DMResourcePoolConfigBuilderException" reason:@"name 不能为空" userInfo:nil];
}
_name = name;
}
- (void)setMaxTotal:(NSInteger)maxTotal
{
if (maxTotal < 0) {
@throw [NSException exceptionWithName:@"DMResourcePoolConfigBuilderException" reason:@"maxTotal 不能为负数" userInfo:nil];
} else {
_maxTotal = maxTotal;
}
}
- (void)setMaxIdle:(NSInteger)maxIdle
{
if (maxIdle < 0) {
@throw [NSException exceptionWithName:@"DMResourcePoolConfigBuilderException" reason:@"maxIdle 不能为负数" userInfo:nil];
} else {
_maxIdle = maxIdle;
}
}
- (void)setMinIdle:(NSInteger)minIdle
{
if (minIdle < 0) {
@throw [NSException exceptionWithName:@"DMResourcePoolConfigBuilderException" reason:@"minIdle 不能为负数" userInfo:nil];
} else {
_minIdle = minIdle;
}
}
- (DMResourcePoolConfig *)build
{
// 校验逻辑放到这里来做,包括必填项校验、依赖关系校验、约束条件校验等
if (self.name == nil || self.name.length <= 0) {
// name是必填项
@throw [NSException exceptionWithName:@"DMResourcePoolConfigBuilderException" reason:@"name 不能为空" userInfo:nil];
}
if (self.maxIdle > self.maxTotal) {
@throw [NSException exceptionWithName:@"DMResourcePoolConfigBuilderException" reason:@"maxIdle 小于等于maxTotal" userInfo:nil];
}
if (self.minIdle > self.maxTotal || self.minIdle > self.maxIdle) {
@throw [NSException exceptionWithName:@"DMResourcePoolConfigBuilderException" reason:@"minIdle 小于等于maxTotal、maxIdle" userInfo:nil];
}
return [[DMResourcePoolConfig alloc] initWithBuilder:self];
}
@end
@interface DMResourcePoolConfig : NSObject
@property (nonatomic, strong, readonly) NSString *name;
@property (nonatomic, assign, readonly) NSInteger maxTotal;
@property (nonatomic, assign, readonly) NSInteger maxIdle;
@property (nonatomic, assign, readonly) NSInteger minIdle;
// 建造者模式
- (instancetype)initWithBuilder:(DMResourcePoolConfigBuilder *)builder;
@end
@interface DMResourcePoolConfig ()
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger maxTotal;
@property (nonatomic, assign) NSInteger maxIdle;
@property (nonatomic, assign) NSInteger minIdle;
@end
@implementation DMResourcePoolConfig
- (instancetype)initWithBuilder:(DMResourcePoolConfigBuilder *)builder
{
if (self = [super init]) {
self.name = builder.name;
self.maxTotal = builder.maxTotal;
self.maxIdle = builder.maxIdle;
self.minIdle = builder.minIdle;
}
return self;
}
@end
使用代码如下
DMResourcePoolConfigBuilder *builder = [[DMResourcePoolConfigBuilder alloc] init];
builder.name = @"dbConnectionPool";
builder.maxTotal = 16;
builder.maxIdle = 10;
builder.minIdle = 8;
DMResourcePoolConfig *config = [builder build];
使用建造者模式创建对象,还能避免对象存在无效状态。使用建造者模式来构建对象,代码实际上是有点重复的,DMResourcePoolConfig类中的成员变量,要在Builder类中重新定义一遍。
与工厂模式有何区别?
建造者模式是让建造者类来负责对象的创建工作。工厂模式是由工厂类来负责对象创建的工作。工厂模式是用来创建不同但是相关类型的对象(继承同一父类或者实现同一协议的一组子类),由给定的参数来决定创建那种类型的对象。建造者模式是用来创建一种类型的复杂对象,通过设置不同的可选参数,“定制化”地创建不同的实例对象。
网友评论