假設我們要生產一臺手機,為了方便我們把生產手機的步驟分為三大步:
- 生成cpu
- 生成其他零配件
- 生成螢幕
然後把這三部生成的產品組裝起來就生成了一部手機。假設我們要生成不同品牌的手機那麼就要不斷重複著三個步驟去生成不同的產品然後組裝。可以發現在這個過程中,生成一部手機的步驟永遠是這三個步驟不會改變,改變的是每次生成的產品在不斷變化,然後用這些產品通過這三個步驟組裝出來不同品牌的手機。
我們思考下,在這個過程中,不變的部分是手機這個複雜物件的生產步驟,變化的是組成手機這個物件的零件部分可以有不同的表現形式。那麼我們就可以把不變的部分和變化的部分分離開發,對變化的部分進行封裝,這就要用到我們今天講的生成器模式
。這個模式主要是用來將複雜物件的生產過程和表示分離開來,讓相同的生成過程可以構建出來不同表現形式的物件。
1、定義
將一個複雜物件的構建和表現分離,讓相同的構建過程可以建立不同的表示
首先,我們注意上面的限定詞:複雜物件
,這表示這個物件的建立過程比較繁瑣,可以分為不同的小步驟建立組成部分,然後通過組合這些小步驟來完成一個完整物件的建立。所以簡單的物件建立就不用使用這個模式啦。
其次,即使是複雜的物件,但是隻有一種表現形式也沒必要用,只有當你需要建立同一個系列(很多)的複雜物件的時候,你才有必要把構建過程和表現過程分離,分別封裝起來,讓你的構建過程可以複用,表現過程通過抽象定義每個步驟的方法,讓子類去具體實現這些方法,是每個步驟差異化,從而構建出來不同的產品表現。客戶端只需要面向介面程式設計即可,方便切換到不同的產品。
2、 UML結構圖
3、 實際場景使用
3.1、需求分析
現在有一份文件是純文字格式,需要把它匯出為兩種不同的格式:html和xml格式。
我們來分析下,匯出為兩種不同的格式就相當於生成兩個不同的物件,如果使用常規的做法,我們可能會生成兩個不同的類分別實現匯出到html和xml的需求。這種做法可以滿足目前的需求,但是如果後續要增加匯出到word和RTF等格式,那麼又需要新加兩個類,而且客戶端就必須知道所有的這些類,違反開閉原則,也不適合擴充套件。
這個時候我們可以把文件的生成過程提取出來,假設不管什麼文件的生成步驟都是三個步驟:
- 生成檔案頭
- 生成檔案內容
- 生成檔案尾
而不同的檔案格式僅僅在這三個步驟的表現形式是不同的,那麼我們就可以把匯出檔案的過程分離為兩部分,不變的是構建過程,變換的是每個步驟的表現形式。
這樣以後再新增任何新的檔案到處格式,只需要實現這三個步驟就可以了,方便擴充套件,客戶端此時也不需要知道每種格式的具體實現類,我們會提供一個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模式是通過繼承方式。最主要的區別是兩者的目的完全不同,前者是為了構建複雜物件,後者是為了定義演算法骨架。