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

image

假設咱們要生產一臺手機,爲了方便咱們把生產手機的步驟分爲三大步:javascript

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

而後把這三部生成的產品組裝起來就生成了一部手機。假設咱們要生成不一樣品牌的手機那麼就要不斷重複着三個步驟去生成不一樣的產品而後組裝。能夠發如今這個過程當中,生成一部手機的步驟永遠是這三個步驟不會改變,改變的是每次生成的產品在不斷變化,而後用這些產品經過這三個步驟組裝出來不一樣品牌的手機。html

咱們思考下,在這個過程當中,不變的部分是手機這個複雜對象的生產步驟,變化的是組成手機這個對象的零件部分能夠有不一樣的表現形式。那麼咱們就能夠把不變的部分和變化的部分分離開發,對變化的部分進行封裝,這就要用到咱們今天講的生成器模式。這個模式主要是用來將複雜對象的生產過程和表示分離開來,讓相同的生成過程能夠構建出來不一樣表現形式的對象。java


一、定義

將一個複雜對象的構建和表現分離,讓相同的構建過程能夠建立不一樣的表示算法

首先,咱們注意上面的限定詞:複雜對象,這表示這個對象的建立過程比較繁瑣,能夠分爲不一樣的小步驟建立組成部分,而後經過組合這些小步驟來完成一個完整對象的建立。因此簡單的對象建立就不用使用這個模式啦。編程

其次,即便是複雜的對象,可是隻有一種表現形式也不必用,只有當你須要建立同一個系列(不少)的複雜對象的時候,你纔有必要把構建過程和表現過程分離,分別封裝起來,讓你的構建過程能夠複用,表現過程經過抽象定義每一個步驟的方法,讓子類去具體實現這些方法,是每一個步驟差別化,從而構建出來不一樣的產品表現。客戶端只須要面向接口編程便可,方便切換到不一樣的產品。app

二、 UML結構圖

image


三、 實際場景使用

3.一、需求分析

如今有一份文檔是純文本格式,須要把它導出爲兩種不一樣的格式:html和xml格式。ide

咱們來分析下,導出爲兩種不一樣的格式就至關於生成兩個不一樣的對象,若是使用常規的作法,咱們可能會生成兩個不一樣的類分別實現導出到html和xml的需求。這種作法能夠知足目前的需求,可是若是後續要增長導出到word和RTF等格式,那麼又須要新加兩個類,並且客戶端就必須知道全部的這些類,違反開閉原則,也不適合擴展。ui

這個時候咱們能夠把文檔的生成過程提取出來,假設無論什麼文檔的生成步驟都是三個步驟:atom

  1. 生成文件頭
  2. 生成文件內容
  3. 生成文件尾

而不一樣的文件格式僅僅在這三個步驟的表現形式是不一樣的,那麼咱們就能夠把導出文件的過程分離爲兩部分,不變的是構建過程,變換的是每一個步驟的表現形式。spa

這樣之後再添加任何新的文件處處格式,只須要實現這三個步驟就能夠了,方便擴展,客戶端此時也不須要知道每種格式的具體實現類,咱們會提供一個director類給客戶端返回他須要的具體對象,這樣也能夠對客戶端屏蔽產品構建過程,由於客戶端不必知道產品構建的具體細節。

下面咱們就來看看具體的代碼實現

3.二、代碼實現

申明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];複製代碼

這個時候若是還須要增長其餘轉換格式,只要構建步驟相似,均可以擴展出新的類。


四、思考

生成器模式的主要做用就是分步驟構建複雜產品,可是要注意一點,這個模式的使用場景:這些產品的構建步驟必須固定不變,把這個不變的部分放到director裏面,獨立出來。變化的是每一個步驟的表現形式,放到bulider裏面。這樣相同的構建步驟就能夠構建出來不一樣的產品。

因此咱們能夠看出生成器模式的意圖在於:

分離構建算法和具體的構建過程,從而使得構建算法能夠重用。而具體的構建過程能夠方便的擴展和切換,從而構建出不一樣的產品。

其實現實場景中的director可能不只僅是調用bulider的幾個方法來組合一個產品,director可能須要進行額外的運算,而後根據須要去調用bulider的部件構造方法,從而構建出具體的產品。

好比說,在director的構建方法constructProduct裏面先進行一些運算,而後根據須要調用bulider的bulidHeader方法把本身計算的結果當作參數傳遞給該方法,而後把該方法的返回值在進行一系列運算,而後得出一個結果再傳遞到下個bulider方法,就這樣穿插調用bulider方法,而後才真正生成須要的產品。


五、對比其餘模式

  • 和工廠模式

    這兩個模式能夠組合使用,由於在具體的bulider實現裏面每一個步驟一般須要生成具體的部件對象,若是有多個同一些列的部件對象,那麼就能夠經過工廠方法來獲取,而後在組裝這些部件

  • 和抽象工廠模式

    這兩個模式比較相似,都是定義一個抽象接口而後讓具體類去實現。不過抽象工廠的實現是建立一系列相關的具體產品,而生成器的具體實現不只僅是建立部件,還要組裝他們,而後生成一個具體的產品。前者是爲了生成一系列相關的產品家族,後者是爲了分步驟組裝部件構成一個具體的產品。

    可是兩者也能夠結合使用,bulider模式須要建立許多部件而後組裝,這些部件一般都是有關聯的對象,那麼就可使用抽象工廠來完成建立過程,而後再組裝。

  • 和模板方法模式

    兩者構成很相似,都是定義一個算法骨架,而後讓其餘類去實現具體的算法。不過bulider模式是經過委託方式,template模式是經過繼承方式。最主要的區別是二者的目的徹底不一樣,前者是爲了構建複雜對象,後者是爲了定義算法骨架。


六、Demo下載地址

建造者模式demo

相關文章
相關標籤/搜索