設(shè)計(jì)模式系列4--生成器模式

image

假設(shè)我們要生產(chǎn)一臺手機(jī),為了方便我們把生產(chǎn)手機(jī)的步驟分為三大步:

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

然后把這三部生成的產(chǎn)品組裝起來就生成了一部手機(jī)。假設(shè)我們要生成不同品牌的手機(jī)那么就要不斷重復(fù)著三個(gè)步驟去生成不同的產(chǎn)品然后組裝。可以發(fā)現(xiàn)在這個(gè)過程中,生成一部手機(jī)的步驟永遠(yuǎn)是這三個(gè)步驟不會改變,改變的是每次生成的產(chǎn)品在不斷變化,然后用這些產(chǎn)品通過這三個(gè)步驟組裝出來不同品牌的手機(jī)。

我們思考下,在這個(gè)過程中,不變的部分是手機(jī)這個(gè)復(fù)雜對象的生產(chǎn)步驟,變化的是組成手機(jī)這個(gè)對象的零件部分可以有不同的表現(xiàn)形式。那么我們就可以把不變的部分和變化的部分分離開發(fā),對變化的部分進(jìn)行封裝,這就要用到我們今天講的生成器模式。這個(gè)模式主要是用來將復(fù)雜對象的生產(chǎn)過程和表示分離開來,讓相同的生成過程可以構(gòu)建出來不同表現(xiàn)形式的對象。


1、定義

將一個(gè)復(fù)雜對象的構(gòu)建和表現(xiàn)分離,讓相同的構(gòu)建過程可以創(chuàng)建不同的表示

首先,我們注意上面的限定詞:復(fù)雜對象,這表示這個(gè)對象的創(chuàng)建過程比較繁瑣,可以分為不同的小步驟創(chuàng)建組成部分,然后通過組合這些小步驟來完成一個(gè)完整對象的創(chuàng)建。所以簡單的對象創(chuàng)建就不用使用這個(gè)模式啦。

其次,即使是復(fù)雜的對象,但是只有一種表現(xiàn)形式也沒必要用,只有當(dāng)你需要創(chuàng)建同一個(gè)系列(很多)的復(fù)雜對象的時(shí)候,你才有必要把構(gòu)建過程和表現(xiàn)過程分離,分別封裝起來,讓你的構(gòu)建過程可以復(fù)用,表現(xiàn)過程通過抽象定義每個(gè)步驟的方法,讓子類去具體實(shí)現(xiàn)這些方法,是每個(gè)步驟差異化,從而構(gòu)建出來不同的產(chǎn)品表現(xiàn)。客戶端只需要面向接口編程即可,方便切換到不同的產(chǎn)品。

2、 UML結(jié)構(gòu)圖

image

3、 實(shí)際場景使用

3.1、需求分析

現(xiàn)在有一份文檔是純文本格式,需要把它導(dǎo)出為兩種不同的格式:html和xml格式。

我們來分析下,導(dǎo)出為兩種不同的格式就相當(dāng)于生成兩個(gè)不同的對象,如果使用常規(guī)的做法,我們可能會生成兩個(gè)不同的類分別實(shí)現(xiàn)導(dǎo)出到html和xml的需求。這種做法可以滿足目前的需求,但是如果后續(xù)要增加導(dǎo)出到word和RTF等格式,那么又需要新加兩個(gè)類,而且客戶端就必須知道所有的這些類,違反開閉原則,也不適合擴(kuò)展。

這個(gè)時(shí)候我們可以把文檔的生成過程提取出來,假設(shè)不管什么文檔的生成步驟都是三個(gè)步驟:

  1. 生成文件頭
  2. 生成文件內(nèi)容
  3. 生成文件尾

而不同的文件格式僅僅在這三個(gè)步驟的表現(xiàn)形式是不同的,那么我們就可以把導(dǎo)出文件的過程分離為兩部分,不變的是構(gòu)建過程,變換的是每個(gè)步驟的表現(xiàn)形式。

這樣以后再添加任何新的文件到處格式,只需要實(shí)現(xiàn)這三個(gè)步驟就可以了,方便擴(kuò)展,客戶端此時(shí)也不需要知道每種格式的具體實(shí)現(xiàn)類,我們會提供一個(gè)director類給客戶端返回他需要的具體對象,這樣也可以對客戶端屏蔽產(chǎn)品構(gòu)建過程,因?yàn)榭蛻舳藳]必要知道產(chǎn)品構(gòu)建的具體細(xì)節(jié)。

下面我們就來看看具體的代碼實(shí)現(xiàn)

3.2、代碼實(shí)現(xiàn)

申明bulider的抽象接口如下:


@protocol bilerInterface <NSObject>

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

-(NSString*)getProduct;

@end

分別實(shí)現(xiàn)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

創(chuàng)建director來定義轉(zhuǎn)換文檔的算法

#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調(diào)用,使用xml轉(zhuǎn)換格式:


#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 = @"生產(chǎn)者模式使用實(shí)踐";
//        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轉(zhuǎn)換格式輸出,只需要如下代碼:

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

這個(gè)時(shí)候如果還需要增加其他轉(zhuǎn)換格式,只要構(gòu)建步驟類似,都可以擴(kuò)展出新的類。


4、思考

生成器模式的主要作用就是分步驟構(gòu)建復(fù)雜產(chǎn)品,但是要注意一點(diǎn),這個(gè)模式的使用場景:這些產(chǎn)品的構(gòu)建步驟必須固定不變,把這個(gè)不變的部分放到director里面,獨(dú)立出來。變化的是每個(gè)步驟的表現(xiàn)形式,放到bulider里面。這樣相同的構(gòu)建步驟就可以構(gòu)建出來不同的產(chǎn)品。

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

分離構(gòu)建算法和具體的構(gòu)建過程,從而使得構(gòu)建算法可以重用。而具體的構(gòu)建過程可以方便的擴(kuò)展和切換,從而構(gòu)建出不同的產(chǎn)品。

其實(shí)現(xiàn)實(shí)場景中的director可能不僅僅是調(diào)用bulider的幾個(gè)方法來組合一個(gè)產(chǎn)品,director可能需要進(jìn)行額外的運(yùn)算,然后根據(jù)需要去調(diào)用bulider的部件構(gòu)造方法,從而構(gòu)建出具體的產(chǎn)品。

比如說,在director的構(gòu)建方法constructProduct里面先進(jìn)行一些運(yùn)算,然后根據(jù)需要調(diào)用bulider的bulidHeader方法把自己計(jì)算的結(jié)果當(dāng)做參數(shù)傳遞給該方法,然后把該方法的返回值在進(jìn)行一系列運(yùn)算,然后得出一個(gè)結(jié)果再傳遞到下個(gè)bulider方法,就這樣穿插調(diào)用bulider方法,然后才真正生成需要的產(chǎn)品。


5、對比其他模式

  • 和工廠模式

    這兩個(gè)模式可以組合使用,因?yàn)樵诰唧w的bulider實(shí)現(xiàn)里面每個(gè)步驟通常需要生成具體的部件對象,如果有多個(gè)同一些列的部件對象,那么就可以通過工廠方法來獲取,然后在組裝這些部件

  • 和抽象工廠模式

    這兩個(gè)模式比較類似,都是定義一個(gè)抽象接口然后讓具體類去實(shí)現(xiàn)。不過抽象工廠的實(shí)現(xiàn)是創(chuàng)建一系列相關(guān)的具體產(chǎn)品,而生成器的具體實(shí)現(xiàn)不僅僅是創(chuàng)建部件,還要組裝他們,然后生成一個(gè)具體的產(chǎn)品。前者是為了生成一系列相關(guān)的產(chǎn)品家族,后者是為了分步驟組裝部件構(gòu)成一個(gè)具體的產(chǎn)品。

    但是二者也可以結(jié)合使用,bulider模式需要創(chuàng)建許多部件然后組裝,這些部件通常都是有關(guān)聯(lián)的對象,那么就可以使用抽象工廠來完成創(chuàng)建過程,然后再組裝。

  • 和模板方法模式

    二者構(gòu)成很類似,都是定義一個(gè)算法骨架,然后讓其他類去實(shí)現(xiàn)具體的算法。不過bulider模式是通過委托方式,template模式是通過繼承方式。最主要的區(qū)別是兩者的目的完全不同,前者是為了構(gòu)建復(fù)雜對象,后者是為了定義算法骨架。


6、Demo下載地址

建造者模式demo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容