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 <Foundation/Foundation.h>

@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 <Foundation/Foundation.h>
#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 <Foundation/Foundation.h>
#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 <Foundation/Foundation.h>

@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 <Foundation/Foundation.h>
#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个,而且这些参数有些是可选的参数,考虑使用构造者模式。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341