假設我們要生產一臺手機,為了方便我們把生產手機的步驟分為三大步:
- 生成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模式是通過繼承方式。最主要的區別是兩者的目的完全不同,前者是為了構建復雜對象,后者是為了定義算法骨架。