iOS设计模式-建造者模式

1. 什么是建造者模式

建造者模式(Builder Pattern) 定义

Separate the construction of a complex object from its representation so that the same constructionprocess can create different representations

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。建造者模式属于对象创建型模式。根据中文翻译的不同,建造者模式又可以称为生成器模式。

2. 角色组成

  • Product: 最终要生成的对象。
  • Builder:构建者的抽象基类(有时会使用接口代替)。其定义了构建Product的抽象步骤,其实体类需要实现这些步骤。其会包含一个用来返回最终产品的方法。
  • ConcreteBuilder: Builder的实现类。
  • Director: 主管类,决定如何构建最终产品的算法。


    建造者模式

3. 代码实现

假设你想买车,心里有个预售大概20万左右的B级车(Car),这个时候你就会到网上(CarDirector)搜索符合预算的车型,搜索到比较心仪的两款车分别为别克的君威和丰田的凯美瑞。但是在裸车的基础上,买车还有很多可选配置,比如天窗,一键启动等,这些都是需要额外加钱的,这些配置项基本都是共通的,我们可以定义一个抽象模拟生成器(CarBuilder)。为了更好的比对两款车加上不同配置后的价格,不同款的车肯定需要有不同的具体生成器君威为(BuickCarBuilder), 凯美瑞为(ToyotaCarBuilder)
代码实现如下:

  1. 定义产品类Car
// Car.h
#import 

@interface Car : NSObject
@property (nonatomic, copy) NSString *baseCar;//裸车(必选)
@property (nonatomic, assign) CGFloat price;//价格(必选)
@property (nonatomic, copy) NSString *brandName;//品牌(必选)
@property (nonatomic, copy) NSString *skylight;//天窗(可选)
@property (nonatomic, copy) NSString *autoStart;//一键启动(可选)
- (instancetype)initWithBaseCar:(NSString *)baseCar price:(CGFloat)price;
@end


// Car.m
#import "Car.h"

@implementation Car

- (instancetype)initWithBaseCar:(NSString *)baseCar price:(CGFloat)price {
    self = [super init];
    if (self) {
        _baseCar = baseCar;
        _price = price;
    }
    return self;
}

- (NSString *)description {
    return [NSString stringWithFormat:@"brandName = %@,baseCar = %@, skylight = %@, autoStart = %@, price = %lf", self.brandName, self.baseCar, self.skylight, self.autoStart, self.price];
}

@end
  1. 定义抽象建造者类CarBuilder,具体建造者类BuickCarBuilder和ToyotaCarBuilder
// CarBuilder.h
#import 
#import "Car.h"

@interface CarBuilder : NSObject
@property (nonatomic, strong) Car *car;

- (instancetype)initWithBaseCar:(NSString *)baseCar price:(CGFloat)price;
- (void)setBrandName;
- (void)addSkylight;
- (void)addAutoStart;
- (Car *)getNewCar;
@end


// CarBuilder.m

#import "CarBuilder.h"

@implementation CarBuilder
- (instancetype)initWithBaseCar:(NSString *)baseCar price:(CGFloat)price {
    self = [super init];
    if (self) {
        _car = [[Car alloc] initWithBaseCar:baseCar price:price];
    }
    return self;
}

- (void)setBrandName {
    NSAssert(false, @"must implement in subClass");
}

- (void)addSkylight {
    NSAssert(false, @"must implement in subClass");
}

- (void)addAutoStart {
    NSAssert(false, @"must implement in subClass");
}

- (Car *)getNewCar {
    return _car;
}

@end
// BuickCarBuiler.h
#import "CarBuilder.h"

@interface BuickCarBuiler : CarBuilder

@end


// BuickCarBuiler.m
#import "BuickCarBuiler.h"

@implementation BuickCarBuiler

- (void)setBrandName {
    self.car.brandName = @"别克";
}

- (void)addSkylight {
    self.car.skylight = @"别克天窗";
    self.car.price += 10000;
}
- (void)addAutoStart {
    self.car.autoStart = @"别克一键启动";
    self.car.price += 12000;
}
@end
// ToyotaCarBuilder.h
#import "CarBuilder.h"

@interface ToyotaCarBuilder : CarBuilder

@end

// ToyotaCarBuilder.m
#import "ToyotaCarBuilder.h"

@implementation ToyotaCarBuilder
- (void)setBrandName {
    self.car.brandName = @"丰田";
}

- (void)addSkylight {
    self.car.skylight = @"丰田天窗";
    self.car.price += 9500;
}
- (void)addAutoStart {
    self.car.autoStart = @"丰田一键启动";
    self.car.price += 13000;
}
@end
  1. 定义主管类CarDirector
// CarDirector.h
#import 
#import "CarBuilder.h"

@interface CarDirector : NSObject

- (instancetype)initWithCarBuilder:(CarBuilder *)carBuilder;
- (void)makeCar;

@end

// CarDirector.m
#import "CarDirector.h"

@interface CarDirector()
@property (nonatomic, strong) CarBuilder *carBuilder;
@end

@implementation CarDirector

- (instancetype)initWithCarBuilder:(CarBuilder *)carBuilder {
    self = [super init];
    if (self) {
        _carBuilder = carBuilder;
    }
    return self;
}

- (void)makeCar {
    [_carBuilder setBrandName];
    [_carBuilder addAutoStart];
    [_carBuilder addSkylight];
}

@end
  1. 调用测试,调用过程为 创建具体建造器--通过建造起初始化主管实例--使用主管实例对产品进行构建--生成产品实例

+ (void)test {
    BuickCarBuiler *buickBuilder = [[BuickCarBuiler alloc] initWithBaseCar:@"君威" price:190000];
    CarDirector *buickDirector = [[CarDirector alloc] initWithCarBuilder:buickBuilder];
    [buickDirector makeCar];
    Car *buickCar = [buickBuilder getNewCar];
    NSLog(@"%@", buickCar);
    
    ToyotaCarBuilder *toyotaBuilder = [[ToyotaCarBuilder alloc] initWithBaseCar:@"凯美瑞" price:200000];
    CarDirector *toyotaDirector = [[CarDirector alloc] initWithCarBuilder:toyotaBuilder];
    [toyotaDirector makeCar];
    Car *toyotaCar = [toyotaBuilder getNewCar];
    NSLog(@"%@", toyotaCar);
}
  1. 执行结果


    运行结果

4. 分析

  • 符合依赖倒置原则,建造者之间相互独立,有利于后续的拓展
  • 将创建过程进行模块化,可以进行自由组合得到自定义产品
  • 封装性好,屏蔽了产品内部组成部分,便于控制细节变化风险
  • 但是如果产品内部变化复杂,将会生成很多建造者类
  • 产品的组成部分必须相同,这限制了其使用范围。

5. 适用范围

  • 创建的对象较复杂,由多个部件构成,各部件面临着复杂的变化
  • 创建过程中,相同的创建部件方法,不同的执行顺序产生不同的结果

6. 和工厂方法异同

  • 两者都是创建型设计模式,都能对创建过程进行有效的封装,做到使用和创建分离
  • 建造者模式中强调的是零件的组装,组装顺序不同对象效能也不同,这才是建造者模式要表达的核心意义
  • 建造者模式最主要的功能是基本方法的调用顺序安排,也就是这些基本方法已经实现了,通俗地说就是零件的装配,顺序不同产生的对象也不同;而工厂方法则重点是创建,创建零件是它的主要职责,组装顺序则不是它关心的。

7. 模式拓展

应用过程中可以根据需要改变,如果创建的产品种类只有一种,只需要一个具体建造者,这时可以省略掉抽象建造者,甚至可以省略掉指挥者角色。


代码示例:
假设我们到KFC吃东西,可选食物有汉堡、可乐、薯条、牛奶等,我们可以通过自助下单机(KFCOrderBuilder)进行自选下单,生成想要的订单(KFCOrder)
代码如下:

  1. 订单类(KFCOrder)
// KFCOrder.h
#import 

@interface KFCOrder : NSObject

@property (nonatomic, strong) NSString *hamburger;
@property (nonatomic, strong) NSString *frenchFries;
@property (nonatomic, strong) NSString *coke;
@property (nonatomic, strong) NSString *milk;
@property (nonatomic, strong) NSString *friedChi;

@end


// KFCOrder.m
#import "KFCOrder.h"

@implementation KFCOrder

- (NSString *)description {
    return [NSString stringWithFormat:@"订单:%@,%@,%@,%@,%@", self.hamburger, self.frenchFries, self.coke, self.friedChi, self.milk];
}

@end
  1. 订单建造器(KFCOrderBuilder),为了更加利于灵活配置,这里采用了链式写法
// KFCOrderBuilder.h
#import 
#import "KFCOrder.h"

@class KFCOrderBuilder;
typedef KFCOrderBuilder *(^orderBuildBlock)(NSString *type, NSInteger count);
@interface KFCOrderBuilder : NSObject

- (KFCOrder *)getKFCOrder;
- (orderBuildBlock)hamburger;
- (orderBuildBlock)frenchFries;
- (orderBuildBlock)friedChi;
- (orderBuildBlock)milk;
- (orderBuildBlock)coke;

@end

// KFCOrderBuilder.m
#import "KFCOrderBuilder.h"

@interface KFCOrderBuilder()
@property (nonatomic, strong) KFCOrder *order;
@end

@implementation KFCOrderBuilder
- (instancetype)init{
    self = [super init];
    if (self) {
        _order = [[KFCOrder alloc] init];
    }
    return self;
}

- (KFCOrder *)getKFCOrder {
    return _order;
}

- (orderBuildBlock)hamburger {
    return ^KFCOrderBuilder* (NSString *hamburger, NSInteger count) {
        self.order.hamburger = [NSString stringWithFormat:@"%@ x%ld", hamburger, count];
        return self;
    };
}

- (orderBuildBlock)frenchFries {
    return ^KFCOrderBuilder* (NSString *frenchFries, NSInteger count) {
        self.order.frenchFries = [NSString stringWithFormat:@"%@ x%ld", frenchFries, count];;
        return self;
    };
}

- (orderBuildBlock)coke {
    return ^KFCOrderBuilder* (NSString *coke, NSInteger count) {
        self.order.coke = [NSString stringWithFormat:@"%@ x%ld", coke, count];;
        return self;
    };
}

- (orderBuildBlock)milk {
    return ^KFCOrderBuilder* (NSString *milk, NSInteger count) {
        self.order.milk = [NSString stringWithFormat:@"%@ x%ld", milk, count];;
        return self;
    };
}

- (orderBuildBlock)friedChi {
    return ^KFCOrderBuilder* (NSString *friedChi, NSInteger count) {
        self.order.friedChi = [NSString stringWithFormat:@"%@ x%ld", friedChi, count];;
        return self;
    };
}
@end
  1. 进行调用,可以看到采取链式的方式生成想要的订单
+ (void)test1 {
    KFCOrder *order = [[KFCOrderBuilder alloc] init].hamburger(@"鸡腿堡",2)
                                                    .frenchFries(@"中份薯条",2)
                                                    .coke(@"中可",2)
                                                    .milk(@"鲜奶",1)
                                                    .friedChi(@"炸芝士",2)
                                                    .getKFCOrder;
   
    NSLog(@"%@", order);
}
  1. 运行结果


当一个类的构造函数参数个数超过4个,而且这些参数有些是可选的参数,考虑使用构造者模式。

你可能感兴趣的:(iOS设计模式-建造者模式)