美文网首页
iOS 设计模式之建造者模式

iOS 设计模式之建造者模式

作者: 点滴86 | 来源:发表于2024-04-27 22:48 被阅读0次

建造者模式

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类中重新定义一遍。

与工厂模式有何区别?

建造者模式是让建造者类来负责对象的创建工作。工厂模式是由工厂类来负责对象创建的工作。工厂模式是用来创建不同但是相关类型的对象(继承同一父类或者实现同一协议的一组子类),由给定的参数来决定创建那种类型的对象。建造者模式是用来创建一种类型的复杂对象,通过设置不同的可选参数,“定制化”地创建不同的实例对象。

相关文章

网友评论

      本文标题:iOS 设计模式之建造者模式

      本文链接:https://www.haomeiwen.com/subject/crtzxjtx.html