繼上一篇的面向對象設計的設計原則,本篇是面向對象設計系列的第二個部分:面向對象設計的設計模式的第一篇文章。html
最開始說一下什麼是設計模式。關於設計模式的概念,有不少不一樣的版本,在這裏說一下我我的比較贊同的一個說法:前端
設計模式用於在特定的條件下爲一些重複出現的軟件設計問題提供合理的、有效的解決方案。java
去掉一些定語的修飾,這句話精簡爲:git
設計模式爲問題提供方案。github
簡單來看,設計模式其實就是針對某些問題的一些方案。在軟件開發中,即便不少人在用不一樣的語言去開發不一樣的業務,可是不少時候這些人遇到的問題抽象出來都是類似的。一些卓越的開發者將一些常出現的問題和對應的解決方案彙總起來,總結出了這些設計模式。算法
所以掌握了這些設計模式,可讓咱們更好地去解決開發過程當中遇到的一些常見問題。並且對這些問題的解決方案的掌握程度越好,咱們就越可以打破語言自己的限制去解決問題,也就是加強「軟件開發的內功」。sql
介紹設計模式最著名的一本書莫屬《設計模式 可複用面向對象軟件的基礎》這本書,書中共介紹了23個設計模式。而這些設計模式分爲三大類:數據庫
而本篇做爲該系列的第一篇,講解的是設計模式中的6個建立型設計模式:編程
注意:簡單工廠模式不是 GoF總結出來的23種設計模式之一,不存在於《設計模式 可複用面向對象軟件的基礎》這本書中。設計模式
在面向對象設計中,類與對象幾乎是構成全部系統的基本元素,所以我認爲學好了建立型模式纔是學會設計系統的第一步:由於你應該知道如何去建立一些特定性質的對象,這纔是設計好的系統的開始。
在講解這6個設計模式以前先說一下該系列文章的講解方式:
從更多維度來理解一件事物有助於更深入地理解它,所以每一個設計模式我都會從如下這幾點來說解:
最後一項:「iOS SDK 和 JDK中的應用」講解的是該設計模式在Objective-C和java語言(JDK)中的應用。
首先咱們看一下簡單工廠模式:
簡單工廠模式(Simple Factory Pattern):專門定義一個類(工廠類)來負責建立其餘類的實例。能夠根據建立方法的參數來返回不一樣類的實例,被建立的實例一般都具備共同的父類。
簡單工廠模式又稱爲靜態工廠方法(Static Factory Method)模式,它屬於類建立型模式。
若是咱們但願將一些爲數很少的相似的對象的建立和他們的建立細節分離開,也不須要知道對象的具體類型,可使用簡單工廠模式。
舉個形象點的例子:在前端開發中,經常會使用外觀各式各樣的按鈕:好比有的按鈕有圓角,有的按鈕有陰影,有的按鈕有邊框,有的按鈕無邊框等等。可是由於同一種樣式的按鈕能夠出如今項目的不少地方,因此若是在每一個地方都把建立按鈕的邏輯寫一遍的話顯然是會形成代碼的重複(並且因爲業務的緣由有的按鈕的建立邏輯能比較複雜,代碼量大)。
那麼爲了不重複代碼的產生,咱們能夠將這些建立按鈕的邏輯都放在一個「工廠」裏面,讓這個工廠來根據你的需求(傳入的參數)來建立對應的按鈕並返回給你。這樣一來,一樣類型的按鈕在多個地方使用的時候,就能夠只給這個工廠傳入其對應的參數並拿到返回的按鈕便可。
下面來看一下簡單工廠模式的成員和類圖。
簡單工廠模式的結構比較簡單,一共只有三個成員:
下面經過類圖來看一下各個成員之間的關係:
從類圖中能夠看出,工廠類提供一個靜態方法:經過傳入的字符串來製造其所對應的產品。
舉一個店鋪售賣不一樣品牌手機的例子:店鋪,即客戶端類向手機工廠購進手機售賣。
該場景可使用簡單工廠的角色來設計:
Phone
,是全部具體產品類的父類,提供一個公共接口packaging
表示手機的裝箱並送到店鋪。IPhone
),小米手機類(MIPhone
),華爲手機類(HWPhone
)。PhoneFactory
根據不一樣的參數來建立不一樣的手機。Store
負責售賣手機。抽象產品類Phone
:
//================== Phone.h ==================
@interface Phone : NSObject
//package to store
- (void)packaging;
@end
複製代碼
具體產品類 IPhone
:
//================== IPhone.h ==================
@interface IPhone : Phone
@end
//================== IPhone.m ==================
@implementation IPhone
- (void)packaging{
NSLog(@"IPhone has been packaged");
}
@end
複製代碼
具體產品類 MIPhone
:
//================== MIPhone.h ==================
@interface MIPhone : Phone
@end
//================== MIPhone.m ==================
@implementation MIPhone
- (void)packaging{
NSLog(@"MIPhone has been packaged");
}
@end
複製代碼
具體產品類:HWPhone
:
//================== HWPhone.h ==================
@interface HWPhone : Phone
@end
//================== HWPhone.m ==================
@implementation HWPhone
- (void)packaging{
NSLog(@"HUAWEI Phone has been packaged");
}
@end
複製代碼
以上是抽象產品類以及它的三個子類:蘋果手機類,小米手機類和華爲手機類。 下面看一下工廠類 PhoneFactory
:
//================== PhoneFactory.h ==================
@interface PhoneFactory : NSObject
+ (Phone *)createPhoneWithTag:(NSString *)tag;
@end
//================== PhoneFactory.m ==================
#import "IPhone.h"
#import "MIPhone.h"
#import "HWPhone.h"
@implementation PhoneFactory
+ (Phone *)createPhoneWithTag:(NSString *)tag{
if ([tag isEqualToString:@"i"]) {
IPhone *iphone = [[IPhone alloc] init];
return iphone;
}else if ([tag isEqualToString:@"MI"]){
MIPhone *miPhone = [[MIPhone alloc] init];
return miPhone;
}else if ([tag isEqualToString:@"HW"]){
HWPhone *hwPhone = [[HWPhone alloc] init];
return hwPhone;
}else{
return nil;
}
}
@end
複製代碼
工廠類向外部(客戶端)提供了一個創造手機的接口
createPhoneWithTag:
,根據傳入參數的不一樣能夠返回不一樣的具體產品類。所以客戶端只須要知道它所須要的產品所對應的參數便可得到對應的產品了。
在本例中,咱們聲明瞭店鋪類 Store
爲客戶端類:
//================== Store.h ==================
#import "Phone.h"
@interface Store : NSObject
- (void)sellPhone:(Phone *)phone;
@end
//================== Store.m ==================
@implementation Store
- (void)sellPhone:(Phone *)phone{
NSLog(@"Store begins to sell phone:%@",[phone class]);
}
@end
複製代碼
客戶端類聲明瞭一個售賣手機的接口
sellPhone:
。表示它能夠售賣做爲參數所傳入的手機。
最後咱們用代碼模擬一下這個實際場景:
//================== Using by client ==================
//1. A phone store wants to sell iPhone
Store *phoneStore = [[Store alloc] init];
//2. create phone
Phone *iPhone = [PhoneFactory createPhoneWithTag:@"i"];
//3. package phone to store
[iphone packaging];
//4. store sells phone after receving it
[phoneStore sellPhone:iphone];
複製代碼
上面代碼的解讀:
i
。在這裏咱們須要注意的是:商店從工廠拿到手機不須要了解手機制做的過程,只須要知道它要工廠作的是手機(只知道Phone
類便可),和須要給工廠類傳入它所需手機所對應的參數便可(這裏的iPhone手機對應的參數就是i
)。
下面咱們看一下該例子對應的 UML類圖,能夠更直觀地看一下各個成員之間的關係:
NSNumber
的工廠方法傳入不一樣類型的數據,則會返回不一樣數據所對應的NSNumber
的子類。Calendar
類中的私有的createCalendar(TimeZone zone, Locale aLocale)
方法經過不一樣的入參來返回不一樣類型的Calendar子類的實例。工廠方法模式(Factory Method Pattern)又稱爲工廠模式,工廠父類負責定義建立產品對象的公共接口,而工廠子類則負責生成具體的產品對象,即經過不一樣的工廠子類來建立不一樣的產品對象。
工廠方法模式的適用場景與簡單工廠相似,都是建立數據和行爲比較相似的對象。可是和簡單工廠不一樣的是:在工廠方法模式中,由於建立對象的責任移交給了抽象工廠的子類,所以客戶端須要知道其所需產品所對應的工廠子類,而不是簡單工廠中的參數。
下面咱們看一下工廠方法模式的成員和類圖。
工廠方法模式包含四個成員:
下面經過類圖來看一下各個成員之間的關係:
從類圖中咱們能夠看到:抽象工廠負責定義具體工廠必須實現的接口,而建立產品對象的任務則交給具體工廠,由特定的子工廠來建立其對應的產品。
這使得工廠方法模式能夠容許系統在不修改原有工廠的狀況下引進新產品:只須要建立新產品類和其所對應的工廠類便可。
一樣也是模擬上面的簡單工廠例子中的場景(手機商店賣手機),可是因爲此次是由工廠方法模式來實現的,所以在代碼設計上會有變化。
與簡單工廠模式不一樣的是:簡單工廠模式裏面只有一個工廠,而工廠方法模式裏面有一個抽象工廠和繼承於它的具體工廠。
所以一樣的三個品牌的手機,咱們能夠經過三個不一樣的具體工廠:蘋果手機工廠(IPhoneFactory
),小米手機工廠 (MIPhoneFactory
),華爲手機工廠(HWPhoneFactory
)來生產。而這些具體工廠類都會繼承於抽象手機工廠類:PhoneFactory
,它來聲明生產手機的接口。
下面咱們用代碼來具體來看一下工廠類(抽象工廠和具體工廠)的設計:
首先咱們聲明一個抽象工廠類 PhoneFactory
:
//================== PhoneFactory.h ==================
#import "Phone.h"
@interface PhoneFactory : NSObject
+ (Phone *)createPhone;
@end
//================== PhoneFactory.m ==================
@implementation PhoneFactory
+ (Phone *)createPhone{
//implemented by subclass
return nil;
}
@end
複製代碼
抽象工廠類給具體工廠提供了生產手機的接口,所以不一樣的具體工廠能夠按照本身的方式來生產手機。下面看一下具體工廠:
蘋果手機工廠 IPhoneFactory
//================== IPhoneFactory.h ==================
@interface IPhoneFactory : PhoneFactory
@end
//================== IPhoneFactory.m ==================
#import "IPhone.h"
@implementation IPhoneFactory
+ (Phone *)createPhone{
IPhone *iphone = [[IPhone alloc] init];
NSLog(@"iPhone has been created");
return iphone;
}
@end
複製代碼
小米手機工廠 MIPhoneFactory
:
//================== MIPhoneFactory.h ==================
@interface MPhoneFactory : PhoneFactory
@end
//================== MIPhoneFactory.m ==================
#import "MiPhone.h"
@implementation MPhoneFactory
+ (Phone *)createPhone{
MiPhone *miPhone = [[MiPhone alloc] init];
NSLog(@"MIPhone has been created");
return miPhone;
}
@end
複製代碼
華爲手機工廠 HWPhoneFactory
:
//================== HWPhoneFactory.h ==================
@interface HWPhoneFactory : PhoneFactory
@end
//================== HWPhoneFactory.m ==================
#import "HWPhone.h"
@implementation HWPhoneFactory
+ (Phone *)createPhone{
HWPhone *hwPhone = [[HWPhone alloc] init];
NSLog(@"HWPhone has been created");
return hwPhone;
}
@end
複製代碼
以上就是聲明的抽象工廠類和具體工廠類。由於生產手機的責任分配給了各個具體工廠類,所以客戶端只須要委託所需手機所對應的工廠就能夠得到其生產的手機了。
由於抽象產品類
Phone
和三個具體產品類(IPhone
,MIPhone
,HWPhone
)和簡單工廠模式中介紹的例子中的同樣,所以這裏就再也不重複介紹了。
下面咱們用代碼模擬一下該場景:
//================== Using by client ==================
//A phone store
Store *phoneStore = [[Store alloc] init];
//phoneStore wants to sell iphone
Phone *iphone = [IPhoneFactory createPhone];
[iphone packaging];
[phoneStore sellPhone:iphone];
//phoneStore wants to sell MIPhone
Phone *miPhone = [MPhoneFactory createPhone];
[miPhone packaging];
[phoneStore sellPhone:miPhone];
//phoneStore wants to sell HWPhone
Phone *hwPhone = [HWPhoneFactory createPhone];
[hwPhone packaging];
[phoneStore sellPhone:hwPhone];
複製代碼
由上面的代碼能夠看出:客戶端phoneStore
只需委託iPhone,MIPhone,HWPhone對應的工廠便可得到對應的手機了。
並且之後若是增長其餘牌子的手機,例如魅族手機,就能夠聲明一個魅族手機類和魅族手機的工廠類並實現createPhone
這個方法便可,而不須要改動原有已經聲明好的各個手機類和具體工廠類。
下面咱們看一下該例子對應的 UML類圖,能夠更直觀地看一下各個成員之間的關係:
Collection
接口聲明瞭iterator()
方法,該方法返回結果的抽象類是Iterator
。ArrayList
就實現了這個接口;,而ArrayList對應的具體產品是Itr
。抽象工廠模式(Abstract Factory Pattern):提供一個建立一系列相關或相互依賴對象的接口,而無須指定它們具體的類。
有時候咱們須要一個工廠能夠提供多個產品對象,而不是單一的產品對象。好比系統中有多於一個的產品族,而每次只使用其中某一產品族,屬於同一個產品族的產品將在一塊兒使用。
在這裏說一下產品族和產品等級結構的概念:
用一張圖來幫助理解:
在上圖中:
下面再舉一個例子幫助你們理解:
咱們將小米,華爲,蘋果公司比做抽象工廠方法裏的工廠:這三個工廠都有本身生產的手機,平板和電腦。 那麼小米手機,小米平板,小米電腦就屬於小米這個工廠的產品族;一樣適用於華爲工廠和蘋果工廠。 而小米手機,華爲手機,蘋果手機則屬於同一產品等級結構:手機的產品等級結構;平板和電腦也是如此。
結合這個例子對上面的圖作一個修改能夠更形象地理解抽象工廠方法的設計:
上面的關於產品族和產品等級結構的說法參考了慕課網實戰課程:java設計模式精講 Debug 方式+內存分析的6-1節。
抽象工廠模式的成員和工廠方法模式的成員是同樣的,只不過抽象工廠方法裏的工廠是面向產品族的。
下面經過類圖來看一下各個成員之間的關係:
因爲抽象工廠方法裏的工廠是面向產品族的,因此爲了貼合抽象工廠方法的特色,咱們將上面的場景作一下調整:在上面兩個例子中,商店只賣手機。在這個例子中咱們讓商店也賣電腦:分別是蘋果電腦,小米電腦,華爲電腦。
若是咱們仍是套用上面介紹過的工廠方法模式來實現該場景的話,則須要建立三個電腦產品對應的工廠:蘋果電腦工廠,小米電腦工廠,華爲電腦工廠。這就致使類的個數直線上升,之後若是還增長其餘的產品,還須要添加其對應的工廠類,這顯然是不夠優雅的。
仔細看一下這六個產品的特色,咱們能夠把這它們劃分在三個產品族裏面:
而抽象方法偏偏是面向產品族設計的,所以該場景適合使用的是抽象工廠方法。下面結合代碼來看一下該如何設計。
首先引入電腦的基類和各個品牌的電腦類:
電腦基類:
//================== Computer.h ==================
@interface Computer : NSObject
//package to store
- (void)packaging;
@end
//================== Computer.m ==================
@implementation Computer
- (void)packaging{
//implemented by subclass
}
@end
複製代碼
蘋果電腦類 MacBookComputer
:
//================== MacBookComputer.h ==================
@interface MacBookComputer : Computer
@end
//================== MacBookComputer.m ==================
@implementation MacBookComputer
- (void)packaging{
NSLog(@"MacBookComputer has been packaged");
}
@end
複製代碼
小米電腦類 MIComputer
:
//================== MIComputer.h ==================
@interface MIComputer : Computer
@end
//================== MIComputer.m ==================
@implementation MIComputer
- (void)packaging{
NSLog(@"MIComputer has been packaged");
}
@end
複製代碼
華爲電腦類 MateBookComputer
:
//================== MateBookComputer.h ==================
@interface MateBookComputer : Computer
@end
//================== MateBookComputer.m ==================
@implementation MateBookComputer
- (void)packaging{
NSLog(@"MateBookComputer has been packaged");
}
@end
複製代碼
引入電腦相關產品類之後,咱們須要從新設計工廠類。由於抽象工廠方法模式的工廠是面向產品族的,因此抽象工廠方法模式裏的工廠所建立的是同一產品族的產品。下面咱們看一下抽象工廠方法模式的工廠該如何設計:
首先建立全部工廠都須要集成的抽象工廠,它聲明瞭生產同一產品族的全部產品的接口:
//================== Factory.h ==================
#import "Phone.h"
#import "Computer.h"
@interface Factory : NSObject
+ (Phone *)createPhone;
+ (Computer *)createComputer;
@end
//================== Factory.m ==================
@implementation Factory
+ (Phone *)createPhone{
//implemented by subclass
return nil;
}
+ (Computer *)createComputer{
//implemented by subclass
return nil;
}
@end
複製代碼
接着,根據不一樣的產品族,咱們建立不一樣的具體工廠:
首先是蘋果產品族工廠 AppleFactory
:
//================== AppleFactory.h ==================
@interface AppleFactory : Factory
@end
//================== AppleFactory.m ==================
#import "IPhone.h"
#import "MacBookComputer.h"
@implementation AppleFactory
+ (Phone *)createPhone{
IPhone *iPhone = [[IPhone alloc] init];
NSLog(@"iPhone has been created");
return iPhone;
}
+ (Computer *)createComputer{
MacBookComputer *macbook = [[MacBookComputer alloc] init];
NSLog(@"Macbook has been created");
return macbook;
}
@end
複製代碼
接着是小米產品族工廠 MIFactory
:
//================== MIFactory.h ==================
@interface MIFactory : Factory
@end
//================== MIFactory.m ==================
#import "MIPhone.h"
#import "MIComputer.h"
@implementation MIFactory
+ (Phone *)createPhone{
MIPhone *miPhone = [[MIPhone alloc] init];
NSLog(@"MIPhone has been created");
return miPhone;
}
+ (Computer *)createComputer{
MIComputer *miComputer = [[MIComputer alloc] init];
NSLog(@"MIComputer has been created");
return miComputer;
}
@end
複製代碼
最後是華爲產品族工廠 HWFactory
:
//================== HWFactory.h ==================
@interface HWFactory : Factory
@end
//================== HWFactory.m ==================
#import "HWPhone.h"
#import "MateBookComputer.h"
@implementation HWFactory
+ (Phone *)createPhone{
HWPhone *hwPhone = [[HWPhone alloc] init];
NSLog(@"HWPhone has been created");
return hwPhone;
}
+ (Computer *)createComputer{
MateBookComputer *hwComputer = [[MateBookComputer alloc] init];
NSLog(@"HWComputer has been created");
return hwComputer;
}
@end
複製代碼
以上就是工廠類的設計。這樣設計好以後,客戶端若是須要哪一產品族的某個產品的話,只須要找到對應產品族工廠後,調用生產該產品的接口便可。假如須要蘋果電腦,只須要委託蘋果工廠來製造蘋果電腦便可;若是須要小米手機,只須要委託小米工廠製造小米手機便可。
下面用代碼來模擬一下這個場景:
//================== Using by client ==================
Store *store = [[Store alloc] init];
//Store wants to sell MacBook
Computer *macBook = [AppleFactory createComputer];
[macBook packaging];
[store sellComputer:macBook];
//Store wants to sell MIPhone
Phone *miPhone = [MIFactory createPhone];
[miPhone packaging];
[store sellPhone:miPhone];
//Store wants to sell MateBook
Computer *mateBook = [HWFactory createComputer];
[mateBook packaging];
[store sellComputer:mateBook];
複製代碼
上面的代碼就是模擬了商店售賣蘋果電腦,小米手機,華爲電腦的場景。而從此若是該商店引入了新品牌的產品,好比聯想手機,聯想電腦,那麼咱們只須要新增聯想手機類,聯想電腦類,聯想工廠類便可。
下面咱們看一下該例子對應的 UML類圖,能夠更直觀地看一下各個成員之間的關係:
因爲三個工廠的產品總數過多,所以在這裏只體現了蘋果工廠和小米工廠的產品。
Connection
。在這個接口裏面有createStatement()
和prepareStatement(String sql)
。這兩個接口都是獲取的統一產品族的對象,好比MySql和PostgreSQL產品族,具體返回的是哪一個產品族對象,取決於所鏈接的數據庫類型。OK,到如今三個工廠模式已經講完了。在繼續講解下面三個設計模式以前,先簡單回顧一下上面講解的三個工廠模式:
大致上看,簡單工廠模式,工廠方法模式和抽象工廠模式的複雜程度是逐漸升高的。
在實際開發過程當中,咱們須要根據業務場景的複雜程度的不一樣來採用最適合的工廠模式。
單例模式(Singleton Pattern):單例模式確保某一個類只有一個實例,並提供一個訪問它的全劇訪問點。
系統只須要一個實例對象,客戶調用類的單個實例只容許使用一個公共訪問點,除了該公共訪問點,不能經過其餘途徑訪問該實例。比較典型的例子是音樂播放器,日誌系統類等等。
單例模式只有一個成員,就是單例類。由於只有一個成員,因此該設計模式的類圖比較簡單:
通常來講單例類會給外部提供一個獲取單例對象的方法,內部會用靜態對象的方式保存這個對象。
在這裏咱們建立一個簡單的打印日至或上報日至的日至管理單例。
在建立單例時,除了要保證提供惟一實例對象之外,還需注意多線程的問題。下面用代碼來看一下。
建立單例類 LogManager
//================== LogManager.h ==================
@interface LogManager : NSObject
+(instancetype)sharedInstance;
- (void)printLog:(NSString *)logMessage;
- (void)uploadLog:(NSString *)logMessage;
@end
//================== LogManager.m ==================
@implementation LogManager
static LogManager* _sharedInstance = nil;
+(instancetype)sharedInstance
{
static dispatch_once_t onceToken ;
dispatch_once(&onceToken, ^{
_sharedInstance = [[super allocWithZone:NULL] init] ;
}) ;
return _sharedInstance ;
}
+(id)allocWithZone:(struct _NSZone *)zone
{
return [LogManager sharedInstance] ;
}
-(id)copyWithZone:(struct _NSZone *)zone
{
return [LogManager sharedInstance];
}
-(id)mutableCopyWithZone:(NSZone *)zone
{
return [LogManager sharedInstance];
}
- (void)printLog:(NSString *)logMessage{
//print logMessage
}
- (void)uploadLog:(NSString *)logMessage{
//upload logMessage
}
@end
複製代碼
從上面的代碼中能夠看到:
sharedInstance
方法是向外部提供的獲取惟一的實例對象的方法,也是該類中的其餘能夠建立對象的方法的都調用的方法。在這個方法內部使用了dispatch_once
函數來避免多線程訪問致使建立多個實例的狀況。alloc init
出初始化方法能夠返回同一個實例對象,在allocWithZone:
方法裏面仍然調用了sharedInstance
方法。copy
和mutableCopy
方法也能夠返回同一個實例對象,在copyWithZone:
與mutableCopyWithZone
也是調用了sharedInstance
方法。下面分別用這些接口來驗證一下實例的惟一性:
//================== Using by client ==================
//alloc&init
LogManager *manager0 = [[LogManager alloc] init];
//sharedInstance
LogManager *manager1 = [LogManager sharedInstance];
//copy
LogManager *manager2 = [manager0 copy];
//mutableCopy
LogManager *manager3 = [manager1 mutableCopy];
NSLog(@"\nalloc&init: %p\nsharedInstance: %p\ncopy: %p\nmutableCopy: %p",manager0,manager1,manager2,manager3);
複製代碼
咱們看一下打印出來的四個指針所指向對象的地址:
alloc&init: 0x60000000f7e0
sharedInstance: 0x60000000f7e0
copy: 0x60000000f7e0
mutableCopy: 0x60000000f7e0
複製代碼
能夠看出打印出來的地址都相同,說明都是同一對象,證實了實現方法的正確性。
下面咱們看一下該例子對應的 UML類圖,能夠更直觀地看一下各個成員之間的關係:
NSUserDefaults
(key-value持久化)和UIApplication
類(表明應用程序,能夠處理一些點擊事件等)。Runtime
類(表明應用程序的運行環境,使應用程序可以與其運行的環境相鏈接);Desktop
類(容許 Java 應用程序啓動已在本機桌面上註冊的關聯應用程序)生成器模式(Builder Pattern):也叫建立者模式,它將一個複雜對象的構建與它的表示分離,使得一樣的構建過程能夠建立不一樣的表示。
具體點說就是:有些對象的建立流程是同樣的,可是由於自身特性的不一樣,因此在建立他們的時候須要將建立過程和特性的定製分離開來。
下面咱們看一下該設計模式的適用場景。
當建立複雜對象的算法應該獨立於該對象的組成部分以及它們的裝配方式時比較適合使用生成器模式。
一些複雜的對象,它們擁有多個組成部分(如汽車,它包括車輪、方向盤、發送機等各類部件)。而對於大多數用戶而言,無須知道這些部件的裝配細節,也幾乎不會使用單獨某個部件,而是使用一輛完整的汽車。並且這些部分的建立順序是固定的,或者是須要指定的。
在這種狀況下能夠經過建造者模式對其進行設計與描述,生成器模式能夠將部件和其組裝過程分開,一步一步建立一個複雜的對象。
建造者模式包含4個成員:
下面經過類圖來看一下各個成員之間的關係:
須要注意的是:
- Builder類中的product成員變量的關鍵字爲
protected
,目的是爲了僅讓它和它的子類能夠訪問該成員變量。- Director類中的
constructProductWithBuilder(Builder builder)
方法是經過傳入不一樣的builder來構造產品的。並且它的getProduct()
方法同時也封裝了Concrete Builder
類的getProduct()
方法,目的是爲了讓客戶端直接從Director
拿到對應的產品(有些資料裏面的Director
類沒有封裝Concrete Builder
類的getProduct()
方法)。
模擬一個製造手機的場景:手機的組裝須要幾個固定的零件:CPU,RAM,屏幕,攝像頭,並且須要CPU -> RAM ->屏幕 -> 攝像頭的順序來製造。
咱們使用建造者設計模式來實現這個場景:首先不一樣的手機要匹配不一樣的builder;而後在Director
類裏面來定義製造順序。
首先咱們定義手機這個類,它有幾個屬性:
//================== Phone.h ==================
@interface Phone : NSObject
@property (nonatomic, copy) NSString *cpu;
@property (nonatomic, copy) NSString *capacity;
@property (nonatomic, copy) NSString *display;
@property (nonatomic, copy) NSString *camera;
@end
複製代碼
而後咱們建立抽象builder類:
//================== Builder.h ==================
#import "Phone.h"
@interface Builder : NSObject
{
@protected Phone *_phone;
}
- (void)createPhone;
- (void)buildCPU;
- (void)buildCapacity;
- (void)buildDisplay;
- (void)buildCamera;
- (Phone *)obtainPhone;
@end
複製代碼
抽象builder類聲明瞭建立手機各個組件的接口,也提供了返回手機實例的對象。
接下來咱們建立對應不一樣手機的具體生成者類:
IPhoneXR手機的builder:IPhoneXRBuilder
:
//================== IPhoneXRBuilder.h ==================
@interface IPhoneXRBuilder : Builder
@end
//================== IPhoneXRBuilder.m ==================
@implementation IPhoneXRBuilder
- (void)createPhone{
_phone = [[Phone alloc] init];
}
- (void)buildCPU{
[_phone setCpu:@"A12"];
}
- (void)buildCapacity{
[_phone setCapacity:@"256"];
}
- (void)buildDisplay{
[_phone setDisplay:@"6.1"];
}
- (void)buildCamera{
[_phone setCamera:@"12MP"];
}
- (Phone *)obtainPhone{
return _phone;
}
@end
複製代碼
小米8手機的builder:MI8Builder
:
//================== MI8Builder.h ==================
@interface MI8Builder : Builder
@end
//================== MI8Builder.m ==================
@implementation MI8Builder
- (void)createPhone{
_phone = [[Phone alloc] init];
}
- (void)buildCPU{
[_phone setCpu:@"Snapdragon 845"];
}
- (void)buildCapacity{
[_phone setCapacity:@"128"];
}
- (void)buildDisplay{
[_phone setDisplay:@"6.21"];
}
- (void)buildCamera{
[_phone setCamera:@"12MP"];
}
- (Phone *)obtainPhone{
return _phone;
}
@end
複製代碼
從上面兩個具體builder的代碼能夠看出,這兩個builder都按照其對應的手機配置來建立其對應的手機。
下面來看一下Director的用法:
//================== Director.h ==================
#import "Builder.h"
@interface Director : NSObject
- (void)constructPhoneWithBuilder:(Builder *)builder;
- (Phone *)obtainPhone;
@end
//================== Director.m ==================
implementation Director
{
Builder *_builder;
}
- (void)constructPhoneWithBuilder:(Builder *)builder{
_builder = builder;
[_builder buildCPU];
[_builder buildCapacity];
[_builder buildDisplay];
[_builder buildCamera];
}
- (Phone *)obtainPhone{
return [_builder obtainPhone];
}
@end
複製代碼
Director類提供了
construct:
方法,須要傳入builder的實例。該方法裏面按照既定的順序來建立手機。
最後咱們看一下客戶端是如何使用具體的Builder和Director實例的:
//================== Using by client ==================
//Get iPhoneXR
//1. A director instance
Director *director = [[Director alloc] init];
//2. A builder instance
IPhoneXRBuilder *iphoneXRBuilder = [[IPhoneXRBuilder alloc] init];
//3. Construct phone by director
[director construct:iphoneXRBuilder];
//4. Get phone by builder
Phone *iPhoneXR = [iphoneXRBuilder obtainPhone];
NSLog(@"Get new phone iPhoneXR of data: %@",iPhoneXR);
//Get MI8
MI8Builder *mi8Builder = [[MI8Builder alloc] init];
[director construct:mi8Builder];
Phone *mi8 = [mi8Builder obtainPhone];
NSLog(@"Get new phone MI8 of data: %@",mi8);
複製代碼
從上面能夠看出客戶端獲取具體產品的過程:
- 首先須要實例化一個Director的實例。
- 而後根據所須要的產品找出其對應的builder。
- 將builder傳入director實例的
construct:
方法。- 從builder的
obtainPhone
獲取手機實例。
下面咱們看一下該例子對應的 UML類圖,能夠更直觀地看一下各個成員之間的關係:
建造者模式所建立的產品通常具備較多的共同點,其組成部分類似,若是產品之間的差別性很大,則不適合使用建造者模式,所以其使用範圍受到必定的限制。
若是產品的內部變化複雜,可能會致使須要定義不少具體建造者類來實現這種變化,致使系統變得很龐大。
StringBuilder
屬於builder,它向外部提供append(String)
方法來拼接字符串(也能夠傳入int等其餘類型);而toString()
方法來返回字符串。原型模式(Prototype Pattern): 使用原型實例指定待建立對象的類型,而且經過複製這個原型來建立新的對象。
對象層級嵌套比較多,從零到一建立對象的過程比較繁瑣時,能夠直接經過複製的方式建立新的對象
當一個類的實例只能有幾個不一樣狀態組合中的一種時,咱們能夠利用已有的對象進行復制來得到
原型模式主要包含以下兩個角色:
下面經過類圖來看一下各個成員之間的關係:
須要注意的是,這裏面的clone()
方法返回的是被複製出來的實例對象。
模擬一份校招的簡歷,簡歷裏面有人名,性別,年齡以及學歷相關的信息。這裏面學歷相關的信息又包含學校名稱,專業,開始和截止年限的信息。
這裏的學歷相關信息可使用單獨一個對象來作,所以總體的簡歷對象的結構能夠是:
簡歷對象:
並且由於對於同一學校同一屆的同一專業的畢業生來講,學歷對象中的信息是相同的,這時候若是須要大量生成這些畢業生的簡歷的話比較適合使用原型模式。
首先定義學歷對象:
//================== UniversityInfo.h ==================
@interface UniversityInfo : NSObject<NSCopying>
@property (nonatomic, copy) NSString *universityName;
@property (nonatomic, copy) NSString *startYear;
@property (nonatomic, copy) NSString *endYear;
@property (nonatomic, copy) NSString *major;
- (id)copyWithZone:(NSZone *)zone;
@end
//================== UniversityInfo.m ==================
@implementation UniversityInfo
- (id)copyWithZone:(NSZone *)zone
{
UniversityInfo *infoCopy = [[[self class] allocWithZone:zone] init];
[infoCopy setUniversityName:[_universityName mutableCopy]];
[infoCopy setStartYear:[_startYear mutableCopy]];
[infoCopy setEndYear:[_endYear mutableCopy]];
[infoCopy setMajor:[_major mutableCopy]];
return infoCopy;
}
@end
複製代碼
由於學歷對象是支持複製的,所以須要聽從
<NSCopying>
協議並實現copyWithZone:
方法。並且支持的是深複製,因此在複製NSString的過程當中須要使用mutableCopy
來實現。
接着咱們看一下簡歷對象:
//================== Resume.h ==================
#import "UniversityInfo.h"
@interface Resume : NSObject<NSCopying>
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *gender;
@property (nonatomic, copy) NSString *age;
@property (nonatomic, strong) UniversityInfo *universityInfo;
@end
//================== Resume.m ==================
@implementation Resume
- (id)copyWithZone:(NSZone *)zone
{
Resume *resumeCopy = [[[self class] allocWithZone:zone] init];
[resumeCopy setName:[_name mutableCopy]];
[resumeCopy setGender:[_gender mutableCopy]];
[resumeCopy setAge:[_age mutableCopy]];
[resumeCopy setUniversityInfo:[_universityInfo copy]];
return resumeCopy;
}
@end
複製代碼
一樣地,簡歷對象也須要聽從
<NSCopying>
協議並實現copyWithZone:
方法。
最後咱們看一下複製的效果有沒有達到咱們的預期(被複制對象和複製對象的地址和它們全部的屬性對象的地址都不相同)
//================== Using by client ==================
//resume for LiLei
Resume *resume = [[Resume alloc] init];
resume.name = @"LiLei";
resume.gender = @"male";
resume.age = @"24";
UniversityInfo *info = [[UniversityInfo alloc] init];
info.universityName = @"X";
info.startYear = @"2014";
info.endYear = @"2018";
info.major = @"CS";
resume.universityInfo = info;
//resume_copy for HanMeiMei
Resume *resume_copy = [resume copy];
NSLog(@"\n\n\n======== original resume ======== %@\n\n\n======== copy resume ======== %@",resume,resume_copy);
resume_copy.name = @"HanMeiMei";
resume_copy.gender = @"female";
resume_copy.universityInfo.major = @"TeleCommunication";
NSLog(@"\n\n\n======== original resume ======== %@\n\n\n======== revised copy resume ======== %@",resume,resume_copy);
複製代碼
上面的代碼模擬了這樣一個場景:李雷同窗寫了一份本身的簡歷,而後韓梅梅複製了一份並修改了姓名,性別和專業這三個和李雷不一樣的信息。
這裏咱們重寫了Resume
的description
方法來看一下全部屬性的值及其內存地址。最後來看一下resume對象和resume_copy對象打印的結果:
//================== Output log ==================
======== original resume ========
resume object address:0x604000247d10
name:LiLei | 0x10bc0c0b0
gender:male | 0x10bc0c0d0
age:24 | 0x10bc0c0f0
university name:X| 0x10bc0c110
university start year:2014 | 0x10bc0c130
university end year:2018 | 0x10bc0c150
university major:CS | 0x10bc0c170
======== copy resume ========
resume object address:0x604000247da0
name:LiLei | 0xa000069654c694c5
gender:male | 0xa000000656c616d4
age:24 | 0xa000000000034322
university name:X| 0xa000000000000581
university start year:2014 | 0xa000000343130324
university end year:2018 | 0xa000000383130324
university major:CS | 0xa000000000053432
======== original resume ========
resume object address:0x604000247d10
name:LiLei | 0x10bc0c0b0
gender:male | 0x10bc0c0d0
age:24 | 0x10bc0c0f0
university name:X| 0x10bc0c110
university start year:2014 | 0x10bc0c130
university end year:2018 | 0x10bc0c150
university major:CS | 0x10bc0c170
======== revised copy resume ========
resume object address:0x604000247da0
name:HanMeiMei | 0x10bc0c1b0
gender:female | 0x10bc0c1d0
age:24 | 0xa000000000034322
university name:X| 0xa000000000000581
university start year:2014 | 0xa000000343130324
university end year:2018 | 0xa000000383130324
university major:TeleCommunication | 0x10bc0c1f0
複製代碼
- 上面兩個是原resume和剛被複制後的 copy resume的信息,能夠看出來不管是這兩個對象的地址仍是它們的值對應的地址都是不一樣的,說明成功地實現了深複製。
- 下面兩個是原resume和被修改後的 copy_resume的信息,能夠看出來新的copy_resume的值發生了變化,並且值所對應的地址仍是和原resume的不一樣。
注:還能夠用序列化和反序列化的辦法來實現深複製,由於與代碼設計上不是很複雜,不少語言直接提供了接口,故這裏不作介紹。
下面咱們看一下該例子對應的 UML類圖,能夠更直觀地看一下各個成員之間的關係:
在這裏須要注意的是:
copy
方法是NSObject
類提供的複製本對象的接口。NSObject
相似於Java中的Object
類,在Objective-C中幾乎全部的對象都繼承與它。並且這個copy
方法也相似於Object
類的clone()
方法。copyWithZone(NSZone zone)
方法是接口NSCopying
提供的接口。而由於這個接口存在於實現文件而不是頭文件,因此它不是對外公開的;便是說外部沒法直接調用copyWithZone(NSZone zone)
方法。copyWithZone(NSZone zone)
方法是在上面所說的copy
方法調用後再調用的,做用是將對象的全部數據都進行復制。所以使用者須要在copyWithZone(NSZone zone)
方法裏作工做,而不是copy
方法,這一點和Java的clone
方法不一樣。
<NSCopying>
協議,配合- (id)copyWithZone:(NSZone *)zone
方法; 或者<NSMutableCopying>
協議,配合 copyWithZone:/mutableCopyWithZone:
方法Cloneable
接口並實現clone()
方法來複制該類的實例。到這裏設計模式中的建立型模式就介紹完了,讀者能夠結合UML類圖和demo的代碼來理解每一個設計模式的特色和相互之間的區別,但願讀者能夠有所收穫。
另外,本篇博客的代碼和類圖都保存在個人GitHub庫中:knightsj:object-oriented-design中的Chapter2。
下一篇是面向對象系列的第三篇,講解的是面向對象設計模式中的結構型模式。 該系列的第一篇講解的是設計原則,有興趣的讀者能夠移步:面向對象設計的六大設計原則(附 Demo 及 UML 類圖)
本篇已同步到我的博客:面向對象設計的設計模式(一):建立型模式(附 Demo 及 UML 類圖)
筆者在近期開通了我的公衆號,主要分享編程,讀書筆記,思考類的文章。
由於公衆號天天發佈的消息數有限制,因此到目前爲止尚未將全部過去的精選文章都發布在公衆號上,後續會逐步發佈的。
並且由於各大博客平臺的各類限制,後面還會在公衆號上發佈一些短小精幹,以小見大的乾貨文章哦~
掃下方的公衆號二維碼並點擊關注,期待與您的共同成長~