设计模式系列4--生成器模式

2,102 阅读7分钟

image

假设我们要生产一台手机,为了方便我们把生产手机的步骤分为三大步:

  1. 生成cpu
  2. 生成其他零配件
  3. 生成屏幕

然后把这三部生成的产品组装起来就生成了一部手机。假设我们要生成不同品牌的手机那么就要不断重复着三个步骤去生成不同的产品然后组装。可以发现在这个过程中,生成一部手机的步骤永远是这三个步骤不会改变,改变的是每次生成的产品在不断变化,然后用这些产品通过这三个步骤组装出来不同品牌的手机。

我们思考下,在这个过程中,不变的部分是手机这个复杂对象的生产步骤,变化的是组成手机这个对象的零件部分可以有不同的表现形式。那么我们就可以把不变的部分和变化的部分分离开发,对变化的部分进行封装,这就要用到我们今天讲的生成器模式。这个模式主要是用来将复杂对象的生产过程和表示分离开来,让相同的生成过程可以构建出来不同表现形式的对象。


1、定义

将一个复杂对象的构建和表现分离,让相同的构建过程可以创建不同的表示

首先,我们注意上面的限定词:复杂对象,这表示这个对象的创建过程比较繁琐,可以分为不同的小步骤创建组成部分,然后通过组合这些小步骤来完成一个完整对象的创建。所以简单的对象创建就不用使用这个模式啦。

其次,即使是复杂的对象,但是只有一种表现形式也没必要用,只有当你需要创建同一个系列(很多)的复杂对象的时候,你才有必要把构建过程和表现过程分离,分别封装起来,让你的构建过程可以复用,表现过程通过抽象定义每个步骤的方法,让子类去具体实现这些方法,是每个步骤差异化,从而构建出来不同的产品表现。客户端只需要面向接口编程即可,方便切换到不同的产品。

2、 UML结构图

image


3、 实际场景使用

3.1、需求分析

现在有一份文档是纯文本格式,需要把它导出为两种不同的格式:html和xml格式。

我们来分析下,导出为两种不同的格式就相当于生成两个不同的对象,如果使用常规的做法,我们可能会生成两个不同的类分别实现导出到html和xml的需求。这种做法可以满足目前的需求,但是如果后续要增加导出到word和RTF等格式,那么又需要新加两个类,而且客户端就必须知道所有的这些类,违反开闭原则,也不适合扩展。

这个时候我们可以把文档的生成过程提取出来,假设不管什么文档的生成步骤都是三个步骤:

  1. 生成文件头
  2. 生成文件内容
  3. 生成文件尾

而不同的文件格式仅仅在这三个步骤的表现形式是不同的,那么我们就可以把导出文件的过程分离为两部分,不变的是构建过程,变换的是每个步骤的表现形式。

这样以后再添加任何新的文件到处格式,只需要实现这三个步骤就可以了,方便扩展,客户端此时也不需要知道每种格式的具体实现类,我们会提供一个director类给客户端返回他需要的具体对象,这样也可以对客户端屏蔽产品构建过程,因为客户端没必要知道产品构建的具体细节。

下面我们就来看看具体的代码实现

3.2、代码实现

申明bulider的抽象接口如下:


@protocol bilerInterface <NSObject>

-(void)buildHeader;
-(void)buildBody;
-(void)builFooter;

-(NSString*)getProduct;

@end

分别实现html和xml的具体bulider

#import <Foundation/Foundation.h>
#import "bulierInterface.h"

@interface htmlBuilder : NSObject<bilerInterface>
- (instancetype)initWithData:(NSString *)data;

@end

================
#import "htmlBuilder.h"

@interface htmlBuilder ()
@property(nonatomic,strong)NSMutableString *data;
@end

@implementation htmlBuilder

- (instancetype)initWithData:(NSString *)data
{
    self = [super init];
    if (self) {
        self.data = [[NSMutableString alloc]initWithString:data];
    }
    return self;
}

-(void)buildHeader{
    [self.data insertString:@"\n<html.headr>\n<body>\n" atIndex:0];
}

-(void)buildBody{
    [self.data appendString:@"\n<\\body>\n"];
}

-(void)builFooter{
    [self.data appendString:@"<html.footer>"];
}

-(NSString *)getProduct{
    return self.data;
}
@end

#import <Foundation/Foundation.h>
#import "bulierInterface.h"

@interface XMLBuilder : NSObject<bilerInterface>
- (instancetype)initWithData:(NSString *)data;
@end


===============
#import "XMLBuilder.h"

@interface XMLBuilder()
@property(nonatomic,strong)NSMutableString *data;

@end


@implementation XMLBuilder

- (instancetype)initWithData:(NSString *)data
{
    self = [super init];
    if (self) {
        self.data = [[NSMutableString alloc]initWithString:data];
    }
    return self;
}

-(void)buildHeader{
    [self.data insertString:@"\n<xml.headr>\n<body>\n" atIndex:0];
}

-(void)buildBody{
    [self.data appendString:@"\n<\\body>\n"];
}

-(void)builFooter{
    [self.data appendString:@"<xml.footer>"];
}
-(NSString *)getProduct{
    return self.data;
}


@end

创建director来定义转换文档的算法

#import <Foundation/Foundation.h>
#import "bulierInterface.h"

@interface bulierDirector : NSObject
- (instancetype)initWithBulider:(id<bilerInterface>)bulider;
-(NSString *)constructProduct;
@end


===============

#import "bulierDirector.h"
@interface bulierDirector()
@property(strong,nonatomic)id<bilerInterface> bulider;
@end

@implementation bulierDirector
- (instancetype)initWithBulider:(id<bilerInterface>)bulider
{
    self = [super init];
    if (self) {
        self.bulider = bulider;
    }
    return self;
}

-(NSString *)constructProduct{
    [self.bulider buildHeader];
    [self.bulider buildBody];
    [self.bulider builFooter];
    return  [self.bulider getProduct];
}
@end

client调用,使用xml转换格式:


#import <Foundation/Foundation.h>
#import "bulierInterface.h"
#import "bulierDirector.h"
#import "htmlBuilder.h"
#import "XMLBuilder.h"


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...

        id<bilerInterface> bulider ;
        NSString *data = @"生产者模式使用实践";
//        bulider =  [[htmlBuilder alloc]initWithData:data];
        bulider = [[XMLBuilder alloc]initWithData:data];

        bulierDirector *director = [[bulierDirector alloc]initWithBulider:bulider];
        NSString *str = [director constructProduct];
        NSLog(@"%@", str);
    }
    return 0;
}

如果需要使用html转换格式输出,只需要如下代码:

  bulider =  [[htmlBuilder alloc]initWithData:data];
改为:
  bulider = [[XMLBuilder alloc]initWithData:data];

这个时候如果还需要增加其他转换格式,只要构建步骤类似,都可以扩展出新的类。


4、思考

生成器模式的主要作用就是分步骤构建复杂产品,但是要注意一点,这个模式的使用场景:这些产品的构建步骤必须固定不变,把这个不变的部分放到director里面,独立出来。变化的是每个步骤的表现形式,放到bulider里面。这样相同的构建步骤就可以构建出来不同的产品。

所以我们可以看出生成器模式的意图在于:

分离构建算法和具体的构建过程,从而使得构建算法可以重用。而具体的构建过程可以方便的扩展和切换,从而构建出不同的产品。

其实现实场景中的director可能不仅仅是调用bulider的几个方法来组合一个产品,director可能需要进行额外的运算,然后根据需要去调用bulider的部件构造方法,从而构建出具体的产品。

比如说,在director的构建方法constructProduct里面先进行一些运算,然后根据需要调用bulider的bulidHeader方法把自己计算的结果当做参数传递给该方法,然后把该方法的返回值在进行一系列运算,然后得出一个结果再传递到下个bulider方法,就这样穿插调用bulider方法,然后才真正生成需要的产品。


5、对比其他模式

  • 和工厂模式

    这两个模式可以组合使用,因为在具体的bulider实现里面每个步骤通常需要生成具体的部件对象,如果有多个同一些列的部件对象,那么就可以通过工厂方法来获取,然后在组装这些部件

  • 和抽象工厂模式

    这两个模式比较类似,都是定义一个抽象接口然后让具体类去实现。不过抽象工厂的实现是创建一系列相关的具体产品,而生成器的具体实现不仅仅是创建部件,还要组装他们,然后生成一个具体的产品。前者是为了生成一系列相关的产品家族,后者是为了分步骤组装部件构成一个具体的产品。

    但是二者也可以结合使用,bulider模式需要创建许多部件然后组装,这些部件通常都是有关联的对象,那么就可以使用抽象工厂来完成创建过程,然后再组装。

  • 和模板方法模式

    二者构成很类似,都是定义一个算法骨架,然后让其他类去实现具体的算法。不过bulider模式是通过委托方式,template模式是通过继承方式。最主要的区别是两者的目的完全不同,前者是为了构建复杂对象,后者是为了定义算法骨架。


6、Demo下载地址

建造者模式demo