专栏首页程序员维他命面向对象设计的设计模式(五):生成器模式

面向对象设计的设计模式(五):生成器模式

定义

生成器模式(Builder Pattern):也叫创建者模式,它将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

具体点说就是:有些对象的创建流程是一样的,但是因为自身特性的不同,所以在创建他们的时候需要将创建过程和特性的定制分离开来。

下面我们看一下该设计模式的适用场景。

适用场景

当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时比较适合使用生成器模式。

一些复杂的对象,它们拥有多个组成部分(如汽车,它包括车轮、方向盘、发送机等各种部件)。而对于大多数用户而言,无须知道这些部件的装配细节,也几乎不会使用单独某个部件,而是使用一辆完整的汽车。而且这些部分的创建顺序是固定的,或者是需要指定的。

在这种情况下可以通过建造者模式对其进行设计与描述,生成器模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。

成员与类图

成员

建造者模式包含4个成员:

  1. 抽象建造者(Builder):定义构造产品的几个公共方法。
  2. 具体建造者(ConcreteBuilder):根据不同的需求来实现抽象建造者定义的公共方法;每一个具体建造者都包含一个产品对象作为它的成员变量。
  3. 指挥者(Director):根据传入的具体建造者来返回其所对应的产品对象。
  4. 产品角色(Product):创建的产品。

下面通过类图来看一下各个成员之间的关系:

模式类图

生成器模式类图

需要注意的是:

  • Builder类中的product成员变量的关键字为protected,目的是为了仅让它和它的子类可以访问该成员变量。
  • Director类中的constructProductWithBuilder(Builder builder)方法是通过传入不同的builder来构造产品的。而且它的getProduct()方法同时也封装了Concrete Builder类的getProduct()方法,目的是为了让客户端直接从Director拿到对应的产品(有些资料里面的Director类没有封装Concrete Builder类的getProduct()方法)。

代码示例

场景概述

模拟一个制造手机的场景:手机的组装需要几个固定的零件:CPU,RAM,屏幕,摄像头,而且需要CPU -> RAM ->屏幕 -> 摄像头的顺序来制造。

场景分析

我们使用建造者设计模式来实现这个场景:首先不同的手机要匹配不同的builder;然后在Director类里面来定义制造顺序。

代码实现

首先我们定义手机这个类,它有几个属性:

//================== Phone.h ==================
@interface Phone : NSObject

@property (nonatomic, copy) NSString *cpu;
@property (nonatomic, copy) NSString *capacity;
@property (nonatomic, copy) NSString *display;
@property (nonatomic, copy) NSString *camera;

@end

然后我们创建抽象builder类:

//================== Builder.h ==================
#import "Phone.h"

@interface Builder : NSObject
{
    @protected Phone *_phone;
}

- (void)createPhone;

- (void)buildCPU;
- (void)buildCapacity;
- (void)buildDisplay;
- (void)buildCamera;


- (Phone *)obtainPhone;

@end

抽象builder类声明了创建手机各个组件的接口,也提供了返回手机实例的对象。

接下来我们创建对应不同手机的具体生成者类:

IPhoneXR手机的builder:IPhoneXRBuilder

//================== IPhoneXRBuilder.h ==================
@interface IPhoneXRBuilder : Builder

@end



//================== IPhoneXRBuilder.m ==================
@implementation IPhoneXRBuilder


- (void)createPhone{

    _phone = [[Phone alloc] init];
}


- (void)buildCPU{

    [_phone setCpu:@"A12"];
}

- (void)buildCapacity{

    [_phone setCapacity:@"256"];
}


- (void)buildDisplay{

    [_phone setDisplay:@"6.1"];
}

- (void)buildCamera{

    [_phone setCamera:@"12MP"];
}

- (Phone *)obtainPhone{
    return _phone;
}

@end

小米8手机的builder:MI8Builder

//================== MI8Builder.h ==================
@interface MI8Builder : Builder

@end



//================== MI8Builder.m ==================
@implementation MI8Builder

- (void)createPhone{

    _phone = [[Phone alloc] init];
}


- (void)buildCPU{

    [_phone setCpu:@"Snapdragon 845"];
}

- (void)buildCapacity{

    [_phone setCapacity:@"128"];
}


- (void)buildDisplay{

    [_phone setDisplay:@"6.21"];
}

- (void)buildCamera{

    [_phone setCamera:@"12MP"];
}

- (Phone *)obtainPhone{
    return _phone;
}

@end

从上面两个具体builder的代码可以看出,这两个builder都按照其对应的手机配置来创建其对应的手机。

下面来看一下Director的用法:

//================== Director.h ==================
#import "Builder.h"

@interface Director : NSObject

- (void)constructPhoneWithBuilder:(Builder *)builder;

- (Phone *)obtainPhone;

@end


//================== Director.m ==================
implementation Director
{
    Builder *_builder;
}


- (void)constructPhoneWithBuilder:(Builder *)builder{

    _builder = builder;

    [_builder buildCPU];
    [_builder buildCapacity];
    [_builder buildDisplay];
    [_builder buildCamera];

}


- (Phone *)obtainPhone{

    return [_builder obtainPhone];
}


@end

Director类提供了construct:方法,需要传入builder的实例。该方法里面按照既定的顺序来创建手机。

最后我们看一下客户端是如何使用具体的Builder和Director实例的:

//================== Using by client ==================


//Get iPhoneXR
//1. A director instance
Director *director = [[Director alloc] init];

//2. A builder instance
IPhoneXRBuilder *iphoneXRBuilder = [[IPhoneXRBuilder alloc] init];

//3. Construct phone by director
[director construct:iphoneXRBuilder];

//4. Get phone by builder
Phone *iPhoneXR = [iphoneXRBuilder obtainPhone];
NSLog(@"Get new phone iPhoneXR of data: %@",iPhoneXR);


//Get MI8
MI8Builder *mi8Builder = [[MI8Builder alloc] init];
[director construct:mi8Builder];
Phone *mi8 = [mi8Builder obtainPhone];
NSLog(@"Get new phone MI8      of data: %@",mi8);

从上面可以看出客户端获取具体产品的过程:

  1. 首先需要实例化一个Director的实例。
  2. 然后根据所需要的产品找出其对应的builder。
  3. 将builder传入director实例的construct:方法。
  4. 从builder的obtainPhone获取手机实例。

下面我们看一下该例子对应的 UML类图,可以更直观地看一下各个成员之间的关系:

代码对应的类图

生成器模式代码示例类图

优点

  • 客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
  • 每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者, 用户使用不同的具体建造者即可得到不同的产品对象 。
  • 增加新的具体建造者无须修改原有类库的代码,指挥者类针对抽象建造者类编程,系统扩展方便,符合“开闭原则”。
  • 可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。

缺点

  • 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
  • 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。

iOS SDK 和 JDK 中的应用

  • 暂未发现iOS SDK中使用生成器设计模式的例子,有知道的小伙伴欢迎留言。
  • JDK中的StringBuilder属于builder,它向外部提供append(String)方法来拼接字符串(也可以传入int等其他类型);而toString()方法来返回字符串。

本文分享自微信公众号 - 程序员维他命(J_Knight_),作者:维他命君

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-12-23

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 客户端基本不用的算法系列:快速幂

    幂运算是我们平时写代码的时候最常用的运算之一。根据幂运算的定义我们可以知道,如果我们要求 x 的 N 次幂,那么想当然的就会写出一个 N 次的循环,然后累乘得到...

    用户2932962
  • 面向对象设计的设计模式(七):外观模式

    定义解读:通过这个高层接口,可以将客户端与子系统解耦:客户端可以不直接访问子系统,而是通过外观类间接地访问;同时也可以提高子系统的独立性和可移植性。

    用户2932962
  • 带你入门 DissCode,从而攻克大厂面试题!

    今年七月份,我开始写公众号。有两个目的,第一是为了增加自己在技术圈内的影响力,第二是促进更多人来重视算法。于是我写了一系列文章来讲解一些大学课本上有的但是被很多...

    用户2932962
  • 笔记64 | 个人项目“易来”开发记录《二》处理Fragment中返回的问题

    项勇
  • 微软序列化时出现k_BackingField怎么处理

    当你用微软自带的System.Runtime.Serialization序列化类时,如果出现了json字段中有k_BackingField前缀,请不要见外,因为...

    崔文远TroyCui
  • 算法与数据结构(五):基本排序算法

    排序是将一组无序的数据根据某种规则重新排列成有序的这么一个过程,当时在大学需要我们手工自己实现的主要有三种:选择排序、插入排序和冒泡排序。因为它比较简单,所以这...

    Masimaro
  • Unity游戏开发Photon Server之客户端架构

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明...

    bering
  • 深入理解RESTful API设计

    RESTful API 全称 REpresentational State Transfer (表现层状态转化) 服务器上的文本,图片,网页,视频等都是资源(...

    YingJoy_
  • 6.移植uboot-支持yaffs烧写

    在上一章,裁剪uboot以及分区后,本章主要使uboot支持yaffs以及制作补丁

    张诺谦
  • PostgreSQL的clog—从事务回滚速度谈起

    如果是之前学习别的数据库的人,看PostgreSQL会感觉到有句话非常奇怪:“PostgreSQL的回滚是立即完成的,不会受到事务大小本身的影响”。

    数据和云

扫码关注云+社区

领取腾讯云代金券