設計模式系列4--生成器模式

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,837評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,196評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,688評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,456評論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,955評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,195評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,725評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,608評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,802評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,048評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,424評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,762評論 2 372

推薦閱讀更多精彩內容