本篇是面向對象設計系列文章的第三篇,講解的是設計模式中的結構型模式:html
該系列前面的兩篇文章:java
外觀模式(Facade Pattern):外觀模式定義了一個高層接口,爲子系統中的一組接口提供一個統一的接口。外觀模式又稱爲門面模式,它是一種結構型設計模式模式。git
定義解讀:經過這個高層接口,能夠將客戶端與子系統解耦:客戶端能夠不直接訪問子系統,而是經過外觀類間接地訪問;同時也能夠提升子系統的獨立性和可移植性。github
外觀模式包括客戶端共有三個成員:編程
客戶端類(Client):客戶端是意圖操做子系統的類,它與外觀類直接接觸;與外觀類間接接觸設計模式
外觀類(Facade):外觀類知曉各個子系統的職責和接口,封裝子系統的接口並提供給客戶端數組
子系統類(SubSystem):子系統類實現子系統的功能,對外觀類一無所知緩存
下面經過類圖來看一下各個成員之間的關係:bash
上圖中的
method1&2()
方法就是調用SubSystem1
和SubSystem2
的method1()
和method2()
方法。一樣適用於method2&3()
。網絡
模擬一個智能家居系統。這個智能家居系統能夠用一箇中央遙控器操做其所接入的一些傢俱:檯燈,音箱,空調等等。
在這裏咱們簡單操縱幾個設備:
有的時候,咱們須要某個設備能夠一次執行兩個不一樣的操做;也可能會須要多個設備共同協做來執行一些任務。好比:
假設咱們能夠用遙控器直接開啓熱風,那麼實際上就是兩個步驟:
咱們把這兩個步驟用一個操做包含起來,一步到位。像這樣簡化操做步驟的場景比較適合用外觀模式。
一樣的,咱們想聽歌的話,須要四個步驟:
這些步驟咱們也能夠裝在單獨的一個接口裏面。
相似的,若是咱們想看DVD的話,步驟會更多,由於DVD須要同時輸出聲音和影像:
這些接口也能夠裝在一個單獨的接口裏。
最後,若是咱們要出門,須要關掉全部家用電器,也不須要一個一個將他們關掉,也只須要一個關掉的總接口就行了,由於這個關掉的總接口裏面能夠包含全部家用電器的關閉接口。
所以,這些設備能夠看作是該智能家居系統的子系統;而這個遙控器則扮演的是外觀類的角色。
下面咱們用代碼來看一下如何實現這些設計。
由於全部家用電器都有開啓和關閉的操做,因此咱們先建立一個家用電器的基類HomeDevice
:
//================== HomeDevice.h ==================
//設備基類
@interface HomeDevice : NSObject
//鏈接電源
- (void)on;
//關閉電源
- (void)off;
@end
複製代碼
而後是繼承它的全部家用電器類:
空調類AirConditioner
:
//================== AirConditioner.h ==================
@interface AirConditioner : HomeDevice
//高溫模式
- (void)startHighTemperatureMode;
//常溫模式
- (void)startMiddleTemperatureMode;
//低溫模式
- (void)startLowTemperatureMode;
@end
複製代碼
CD Player類:CDPlayer
:
//================== CDPlayer.h ==================
@interface CDPlayer : HomeDevice
- (void)play;
@end
複製代碼
DVD Player類:DVDPlayer
:
//================== DVDPlayer.h ==================
@interface DVDPlayer : HomeDevice
- (void)play;
@end
複製代碼
音箱類VoiceBox
:
//================== VoiceBox.h ==================
@class CDPlayer;
@class DVDPlayer;
@interface VoiceBox : HomeDevice
//與CDPlayer鏈接
- (void)connetCDPlayer:(CDPlayer *)cdPlayer;
//與CDPlayer斷開鏈接
- (void)disconnetCDPlayer:(CDPlayer *)cdPlayer;
//與DVD Player鏈接
- (void)connetDVDPlayer:(DVDPlayer *)dvdPlayer;
//與DVD Player斷開鏈接
- (void)disconnetDVDPlayer:(DVDPlayer *)dvdPlayer;
@end
複製代碼
投影儀類Projecter
:
//================== Projecter.h ==================
@interface Projecter : HomeDevice
//與DVD Player鏈接
- (void)connetDVDPlayer:(DVDPlayer *)dvdPlayer;
//與DVD Player斷開鏈接
- (void)disconnetDVDPlayer:(DVDPlayer *)dvdPlayer;
@end
複製代碼
注意,音箱是能夠鏈接CD Player和DVD Player的;而投影儀只能鏈接DVD Player
如今咱們把全部的家用電器類和他們的接口都定義好了,下面咱們看一下該實例的外觀類HomeDeviceManager
如何設計。
首先咱們看一下客戶端指望外觀類實現的接口:
//================== HomeDeviceManager.h ==================
@interface HomeDeviceManager : NSObject
//===== 關於空調的接口 =====
//空調吹冷風
- (void)coolWind;
//空調吹熱風
- (void)warmWind;
//===== 關於CD Player的接口 =====
//播放CD
- (void)playMusic;
//關掉音樂
- (void)offMusic;
//===== 關於DVD Player的接口 =====
//播放DVD
- (void)playMovie;
//關閉DVD
- (void)offMoive;
//===== 關於總開關的接口 =====
//打開所有家用電器
- (void)allDeviceOn;
//關閉全部家用電器
- (void)allDeviceOff;
@end
複製代碼
上面的接口分爲了四大類,分別是:
爲了便於讀者理解,這四類的接口所封裝的子系統接口的數量是逐漸增多的。
在看這些接口時如何實現的以前,咱們先看一下外觀類是如何保留這些子系統類的實例的。在該代碼示例中,這些子系統類的實例在外觀類的構造方法裏被建立,並且做爲外觀類的成員變量被保存了下來。
//================== HomeDeviceManager.m ==================
@implementation HomeDeviceManager
{
NSMutableArray *_registeredDevices;//全部註冊(被管理的)的家用電器
AirConditioner *_airconditioner;
CDPlayer *_cdPlayer;
DVDPlayer *_dvdPlayer;
VoiceBox *_voiceBox;
Projecter *_projecter;
}
- (instancetype)init{
self = [super init];
if (self) {
_airconditioner = [[AirConditioner alloc] init];
_cdPlayer = [[CDPlayer alloc] init];
_dvdPlayer = [[DVDPlayer alloc] init];
_voiceBox = [[VoiceBox alloc] init];
_projecter = [[Projecter alloc] init];
_registeredDevices = [NSMutableArray arrayWithArray:@[_airconditioner,
_cdPlayer,
_dvdPlayer,
_voiceBox,
_projecter]];
}
return self;
}
複製代碼
其中 _registeredDevices
這個成員變量是一個數組,它包含了全部和這個外觀類實例關聯的子系統實例。
子系統與外觀類的關聯實現方式不止一種,不做爲本文研究重點,如今只需知道外觀類保留了這些子系統的實例便可。按照順序,咱們首先看一下關於空調的接口的實現:
//================== HomeDeviceManager.m ==================
//空調吹冷風
- (void)coolWind{
[_airconditioner on];
[_airconditioner startLowTemperatureMode];
}
//空調吹熱風
- (void)warmWind{
[_airconditioner on];
[_airconditioner startHighTemperatureMode];
}
複製代碼
吹冷風和吹熱風的接口都包含了空調實例的兩個接口,第一個都是開啓空調,第二個則是對應的冷風和熱風的接口。
咱們接着看關於CD Player的接口的實現:
//================== HomeDeviceManager.m ==================
- (void)playMusic{
//1. 開啓CDPlayer開關
[_cdPlayer on];
//2. 開啓音箱
[_voiceBox on];
//3. 音響與CDPlayer鏈接
[_voiceBox connetCDPlayer:_cdPlayer];
//4. 播放CDPlayer
[_cdPlayer play];
}
//關掉音樂
- (void)offMusic{
//1. 切掉與音箱的鏈接
[_voiceBox disconnetCDPlayer:_cdPlayer];
//2. 關掉音箱
[_voiceBox off];
//3. 關掉CDPlayer
[_cdPlayer off];
}
複製代碼
在上面的場景分析中提到過,聽音樂這個指令要分四個步驟:CD Player和音箱的開啓,兩者的鏈接,以及播放CD Player,這也比較符合實際生活中的場景。關掉音樂也是先斷開鏈接再切斷電源(雖然直接切斷電源也能夠)。
接下來咱們看一下關於DVD Player的接口的實現:
//================== HomeDeviceManager.m ==================
- (void)playMovie{
//1. 開啓DVD player
[_dvdPlayer on];
//2. 開啓音箱
[_voiceBox on];
//3. 音響與DVDPlayer鏈接
[_voiceBox connetDVDPlayer:_dvdPlayer];
//4. 開啓投影儀
[_projecter on];
//5.投影儀與DVDPlayer鏈接
[_projecter connetDVDPlayer:_dvdPlayer];
//6. 播放DVDPlayer
[_dvdPlayer play];
}
- (void)offMoive{
//1. 切掉音箱與DVDPlayer鏈接
[_voiceBox disconnetDVDPlayer:_dvdPlayer];
//2. 關掉音箱
[_voiceBox off];
//3. 切掉投影儀與DVDPlayer鏈接
[_projecter disconnetDVDPlayer:_dvdPlayer];
//4. 關掉投影儀
[_projecter off];
//5. 關掉DVDPlayer
[_dvdPlayer off];
}
複製代碼
由於DVD Player要同時鏈接音箱和投影儀,因此這兩個接口封裝的子系統接口相對於CD Player的更多一些。
最後咱們看一下關於總開關的接口的實現:
//================== HomeDeviceManager.m ==================
//打開所有家用電器
- (void)allDeviceOn{
[_registeredDevices enumerateObjectsUsingBlock:^(HomeDevice *device, NSUInteger idx, BOOL * _Nonnull stop) {
[device on];
}];
}
//關閉全部家用電器
- (void)allDeviceOff{
[_registeredDevices enumerateObjectsUsingBlock:^(HomeDevice *device, NSUInteger idx, BOOL * _Nonnull stop) {
[device off];
}];
}
複製代碼
這兩個接口是爲了方便客戶端開啓和關閉全部設備的,有這兩個接口的話,用戶就不用一一開啓或關閉多個設備了。
關於這兩個接口的實現:
上文說過,該外觀類經過一個數組成員變量_registeredDevices
來保存全部可操做的設備。因此若是咱們須要開啓或關閉全部的設備就能夠遍歷這個數組並向每一個元素調用on
或off
方法。由於這些元素都繼承於HomeDevice
,也就是都有on
或off
方法。
這樣作的好處是,咱們不須要單獨列出全部設備來分別調用它們的接口;並且後面若是添加或者刪除某些設備的話也不須要修改這兩個接口的實現了。
下面咱們看一下該demo多對應的類圖。
從上面的UML類圖中能夠看出,該示例的子系統之間的耦合仍是比較多的;而外觀類
HomeDeviceManager
的接口大大簡化了User
對這些子系統的使用成本。
SDWebImage
封裝了負責圖片下載的類和負責圖片緩存的類,而外部僅向客戶端暴露了簡約的下載圖片的接口。Spring-JDBC
中的JdbcUtils
封裝了Connection
,ResultSet
,Statement
的方法提供給客戶端適配器模式(Adapter Pattern) :將一個接口轉換成客戶但願的另外一個接口,使得本來因爲接口不兼容而不能一塊兒工做的那些類能夠一塊兒工做。適配器模式的別名是包裝器模式(Wrapper),是一種結構型設計模式。
定義解讀:適配器模式又分爲對象適配器和類適配器兩種。
適配器模式有三個成員:
如上文所說,適配器模式分爲類適配器模式和對象適配器模式,所以這裏同時提供這兩種細分模式的 UML類圖。
對象適配器模式:
對象適配器中,被適配者的對象被適配器所持有。當適配器的
request
方法被調用時,在這個方法內部再調用被適配者對應的方法。
類適配器模式:
類適配器中採用了多繼承的方式:適配器同時繼承了目標類和被適配者類,也就都持有了者兩者的方法。
多繼承在Objective-C中能夠經過遵循多個協議來實現,在本模式的代碼示例中只使用對象適配器來實現。
模擬一個替換緩存組件的場景:目前客戶端已經依賴於舊的緩存組件的接口,然後來發現有一個新的緩組件的性能更好一些,須要將舊的緩存組件替換成新的緩存組件,可是新的緩存組件的接口與舊的緩存接口不一致,因此目前來看客戶端是沒法直接與新緩存組件一塊兒工做的。
因爲客戶端在不少地方依賴了舊緩存組件的接口,將這些地方的接口都換成新緩存組件的接口會比較麻煩,並且萬一後面還要換回舊緩存組件或者再換成另一個新的緩存組件的話就還要作重複的事情,這顯然是不夠優雅的。
所以該場景比較適合使用適配器模式:建立一個適配器,讓本來與舊緩存接口的客戶端能夠與新緩存組件一塊兒工做。
在這裏,新的緩存組件就是Adaptee
,舊的緩存組件(接口)就是Target
,由於它是直接和客戶端接觸的。而咱們須要建立一個適配器類Adaptor
來讓客戶端與新緩存組件一塊兒工做。下面用代碼看一下上面的問題如何解決:
首先咱們建立舊緩存組件,並讓客戶端正常使用它。 先建立舊緩存組件的接口OldCacheProtocol
:
對應Java的接口,Objective-C中叫作協議,也就是protocol。
//================== OldCacheProtocol.h ==================
@protocol OldCacheProtocol <NSObject>
- (void)old_saveCacheObject:(id)obj forKey:(NSString *)key;
- (id)old_getCacheObjectForKey:(NSString *)key;
@end
複製代碼
能夠看到該接口包含了兩個操做緩存的方法,方法前綴爲
old
。
再簡單建立一個緩存組件類OldCache
,它實現了OldCacheProtocol
接口:
//================== OldCache.h ==================
@interface OldCache : NSObject <OldCacheProtocol>
@end
//================== OldCache.m ==================
@implementation OldCache
- (void)old_saveCacheObject:(id)obj forKey:(NSString *)key{
NSLog(@"saved cache by old cache object");
}
- (id)old_getCacheObjectForKey:(NSString *)key{
NSString *obj = @"get cache by old cache object";
NSLog(@"%@",obj);
return obj;
}
@end
複製代碼
爲了讀者區分方便,將新舊兩個緩存組件取名爲
NewCache
和OldCache
。實現代碼也比較簡單,由於不是本文介紹的重點,只需區分接口名稱便可。
如今咱們讓客戶端來使用這個舊緩存組件:
//================== client.m ==================
@interface ViewController ()
@property (nonatomic, strong) id<OldCacheProtocol>cache;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//使用舊緩存
[self useOldCache];
//使用緩存組件操做
[self saveObject:@"cache" forKey:@"key"];
}
//實例化舊緩存並保存在``cache``屬性裏
- (void)useOldCache{
self.cache = [[OldCache alloc] init];
}
//使用cache對象
- (void)saveObject:(id)object forKey:(NSString *)key{
[self.cache old_saveCacheObject:object forKey:key];
}
複製代碼
- 在這裏的客戶端就是
ViewController
,它持有一個聽從OldCacheProtocol
協議的實例,也就是說它目前依賴於OldCacheProtocol
的接口。useOldCache
方法用來實例化舊緩存並保存在cache
屬性裏。saveObject:forKey:
方法是真正使用cache對象來保存緩存。
運行並打印一下結果輸出是:saved cache by old cache object
。如今看來客戶端使用舊緩存是沒有問題的。
而如今咱們要加入新的緩存組件了: 首先定義新緩存組件的接口NewCacheProtocol
:
//================== NewCacheProtocol.h ==================
@protocol NewCacheProtocol <NSObject>
- (void)new_saveCacheObject:(id)obj forKey:(NSString *)key;
- (id)new_getCacheObjectForKey:(NSString *)key;
@end
複製代碼
能夠看到,NewCacheProtocol
與OldCacheProtocol
接口大體是類似的,可是名稱仍是不一樣,這裏使用了不一樣的方法前綴作了區分。
接着看一下新緩存組件是如何實現這個接口的:
//================== NewCache.h ==================
@interface NewCache : NSObject <NewCacheProtocol>
@end
//================== NewCache.m ==================
@implementation NewCache
- (void)new_saveCacheObject:(id)obj forKey:(NSString *)key{
NSLog(@"saved cache by new cache object");
}
- (id)new_getCacheObjectForKey:(NSString *)key{
NSString *obj = @"saved cache by new cache object";
NSLog(@"%@",obj);
return obj;
}
@end
複製代碼
如今咱們拿到了新的緩存組件,可是客戶端類目前依賴的是舊的接口,所以適配器類應該上場了:
//================== Adaptor.h ==================
@interface Adaptor : NSObject <OldCacheProtocol>
- (instancetype)initWithNewCache:(NewCache *)newCache;
@end
//================== Adaptor.m ==================
@implementation Adaptor
{
NewCache *_newCache;
}
- (instancetype)initWithNewCache:(NewCache *)newCache{
self = [super init];
if (self) {
_newCache = newCache;
}
return self;
}
- (void)old_saveCacheObject:(id)obj forKey:(NSString *)key{
//transfer responsibility to new cache object
[_newCache new_saveCacheObject:obj forKey:key];
}
- (id)old_getCacheObjectForKey:(NSString *)key{
//transfer responsibility to new cache object
return [_newCache new_getCacheObjectForKey:key];
}
@end
複製代碼
- 首先,適配器類也實現了舊緩存組件的接口;目的是讓它也能夠接收到客戶端操做舊緩存組件的方法。
- 而後,適配器的構造方法裏面須要傳入新組件類的實例;目的是在收到客戶端操做舊緩存組件的命令後,將該命令轉發給新緩存組件類,並調用其對應的方法。
- 最後咱們看一下適配器類是如何實現兩個舊緩存組件的接口的:在
old_saveCacheObject:forKey:
方法中,讓新緩存組件對象調用對應的new_saveCacheObject:forKey:
方法;一樣地,在old_getCacheObjectForKey
方法中,讓新緩存組件對象調用對應的new_getCacheObjectForKey:
方法。
這樣一來,適配器類就定義好了。 那麼最後咱們看一下在客戶端裏面是如何使用適配器的:
//================== client ==================
- (void)viewDidLoad {
[super viewDidLoad];
//使用新緩存組件
[self useNewCache];
[self saveObject:@"cache" forKey:@"key"];
}
- (void)useOldCache{
self.cache = [[OldCache alloc] init];
}
//使用新緩存組件
- (void)useNewCache{
self.cache = [[Adaptor alloc] initWithNewCache:[[NewCache alloc] init]];
}
//使用cache對象
- (void)saveObject:(id)object forKey:(NSString *)key{
[self.cache old_saveCacheObject:object forKey:key];
}
複製代碼
咱們能夠看到,在客戶端裏面,只須要改一處就能夠了:將咱們定義好的適配器類保存在原來的
cache
屬性中就能夠了(useNewCache
方法的實現)。而真正操做緩存的方法saveObject:forKey
不須要有任何改動。
咱們能夠看到,使用適配器模式,客戶端調用舊緩存組件接口的方法都不須要改變;只需稍做處理,就能夠在新舊緩存組件中來回切換,也不須要原來客戶端對緩存的操做。
而之因此能夠作到這麼靈活,其實也是由於在一開始客戶端只是依賴了舊緩存組件類所實現的接口,而不是舊緩存組件類的類型。有心的讀者可能注意到了,上面viewController
的屬性是@property (nonatomic, strong) id<OldCacheProtocol>cache;
。正由於如此,咱們新建的適配器實例才能直接用在這裏,由於適配器類也是實現了<OldCacheProtocol>
接口。相反,若是咱們的cache
屬性是這麼寫的:@property (nonatomic, strong) OldCache *cache;
,即客戶端依賴了舊緩存組件的類型,那麼咱們的適配器類就沒法這麼容易地放在這裏了。所以爲了咱們的程序在未來能夠更好地修改和擴展,依賴接口是一個前提。
下面咱們看一下該代碼示例對應的類圖:
XMLAdapter
使用了適配器模式。橋接模式(Simple Factory Pattern):將抽象部分與它的實現部分分離,使它們均可以獨立地變化。
定義解讀:橋接模式的核心是兩個抽象以組合的形式關聯到一塊兒,從而他們的實現就互不依賴了。
若是一個系統存在兩個獨立變化的維度,並且這兩個維度都須要進行擴展的時候比較適合使用橋接模式。
下面來看一下簡單工廠模式的成員和類圖。
橋接模式一共只有三個成員:
下面經過類圖來看一下各個成員之間的關係:
從類圖中能夠看出
Abstraction
持有Implementor
,可是兩者的實現類互不依賴。這就是橋接模式的核心。
建立一些不一樣的形狀,這些形狀帶有不一樣的顏色。
三種形狀:
三種顏色:
根據上述需求,可能有的朋友會這麼設計:
這樣的設計確實能夠實現上面的需求。可是設想一下,若是後來增長了一種顏色或者形狀的話,是否是要多出來不少類?若是形狀的種類數是m
,顏色的種類數是n
,以這種方式建立的總類數就是 m*n
,當m或n很是大的時候,它們相乘的結果就會變得很大。
咱們觀察一下這個場景:形狀和顏色這兩者的是沒有關聯性的,兩者能夠獨立擴展和變化,這樣的組合比較適合用橋接模式來作。
根據上面提到的橋接模式的成員:
下面咱們用代碼看一下該如何設計。
首先咱們建立形狀的基類Shape
:
//================== Shape.h ==================
@interface Shape : NSObject
{
@protected Color *_color;
}
- (void)renderColor:(Color *)color;
- (void)show;
@end
//================== Shape.m ==================
@implementation Shape
- (void)renderColor:(Color *)color{
_color = color;
}
- (void)show{
NSLog(@"Show %@ with %@",[self class],[_color class]);
}
@end
複製代碼
由上面的代碼能夠看出:
- 形狀類
Shape
持有Color
類的實例,兩者是以組合的形式結合到一塊兒的。並且Shape
類定義了供外部傳入Color
實例的方法renderColor:
:在這個方法裏面接收從外部傳入的Color
實例並保存起來。- 另一個公共接口
show
實際上就是打印這個圖形的名稱及其所搭配的顏色,便於咱們後續驗證。
接着咱們建立三種不一樣的圖形類,它們都繼承於Shape
類:
正方形類:
//================== Square.h ==================
@interface Square : Shape
@end
//================== Square.m ==================
@implementation Square
- (void)show{
[super show];
}
@end
複製代碼
長方形類:
//================== Rectangle.h ==================
@interface Rectangle : Shape
@end
//================== Rectangle.m ==================
@implementation Rectangle
- (void)show{
[super show];
}
@end
複製代碼
圓形類:
//================== Circle.h ==================
@interface Circle : Shape
@end
//================== Circle.m ==================
@implementation Circle
- (void)show{
[super show];
}
@end
複製代碼
還記得上面的Shape
類持有的Color
類麼?它就是全部顏色類的父類:
//================== Color.h ==================
@interface Color : NSObject
@end
//================== Color.m ==================
@implementation Color
@end
複製代碼
接着咱們建立繼承這個Color
類的三個顏色類:
紅色類:
//================== RedColor.h ==================
@interface RedColor : Color
@end
//================== RedColor.m ==================
@implementation RedColor
@end
複製代碼
綠色類:
//================== GreenColor.h ==================
@interface GreenColor : Color
@end
//================== GreenColor.m ==================
@implementation GreenColor
@end
複製代碼
藍色類:
//================== BlueColor.h ==================
@interface BlueColor : Color
@end
//================== BlueColor.m ==================
@implementation BlueColor
@end
複製代碼
OK,到如今全部的形狀類和顏色類的相關類已經建立好了,咱們看一下客戶端是如何使用它們來組合成不一樣的帶有顏色的形狀的:
//================== client ==================
//create 3 shape instances
Rectangle *rect = [[Rectangle alloc] init];
Circle *circle = [[Circle alloc] init];
Square *square = [[Square alloc] init];
//create 3 color instances
RedColor *red = [[RedColor alloc] init];
GreenColor *green = [[GreenColor alloc] init];
BlueColor *blue = [[BlueColor alloc] init];
//rect & red color
[rect renderColor:red];
[rect show];
//rect & green color
[rect renderColor:green];
[rect show];
//circle & blue color
[circle renderColor:blue];
[circle show];
//circle & green color
[circle renderColor:green];
[circle show];
//square & blue color
[square renderColor:blue];
[square show];
//square & red color
[square renderColor:red];
[square show];
複製代碼
上面的代碼裏,咱們先聲明瞭全部的形狀類和顏色類的實例,而後自由搭配,造成不一樣的形狀+顏色的組合。
下面咱們經過打印的結果來看一下組合的效果:
Show Rectangle with RedColor
Show Rectangle with GreenColor
Show Circle with BlueColor
Show Circle with GreenColor
Show Square with BlueColor
Show Square with RedColor
複製代碼
從打印的接口能夠看出組合的結果是沒問題的。
跟上面沒有使用橋接模式的設計相比,使用橋接模式須要的類的總和是 m + n
:當m或n的值很大的時候是遠小於 m * n
(沒有使用橋接,而是使用繼承的方式)的。
並且若是後面還要增長形狀和顏色的話,使用橋接模式就能夠很方便地將原有的形狀和顏色和新的形狀和顏色進行搭配了,新的類和舊的類互不干擾。
下面咱們看一下上面代碼所對應的類圖:
從UML類圖能夠看出,該設計是由兩個抽象層的類
Shape
和Color
構建的,正由於依賴的雙方都是抽象類(而不是具體的實現),並且兩者是以組合的方式聯繫到一塊兒的,因此擴展起來很是方便,互不干擾。這對於從此咱們對代碼的設計有比較好的借鑑意義。
Spring-JDBC
中的DriveManager
經過registerDriver
方法註冊不一樣類型的驅動代理模式(Proxy Pattern) :爲某個對象提供一個代理,並由這個代理對象控制對原對象的訪問。
定義解讀:使用代理模式之後,客戶端直接訪問代理,代理在客戶端和目標對象之間起到中介的做用。
在某些狀況下,一個客戶不想或者不能直接引用一個對象,此時能夠經過一個稱之爲「代理」的第三者來實現間接引用。
由於代理對象能夠在客戶端和目標對象之間起到中介的做用,所以能夠經過代理對象去掉客戶不能看到 的內容和服務或者添加客戶須要的額外服務。
根據業務的不一樣,代理也能夠有不一樣的類型:
下面來看一下代理模式的成員和類圖。
代理模式算上客戶端一共有四個成員:
其實我也不太清楚代理模式裏面爲何會是Subject和RealSubject這個叫法。
下面經過類圖來看一下各個成員之間的關係:
從類圖中能夠看出,工廠類提供一個靜態方法:經過傳入的字符串來製造其所對應的產品。
在這裏舉一個買房者經過買房中介買房的例子。
如今通常咱們買房子不直接接觸房東,而是先接觸中介,買房的相關合同和一些事宜能夠先和中介進行溝通。
在本例中,咱們在這裏讓買房者直接支付費用給中介,而後中介收取一部分的中介費, 再將剩餘的錢交給房東。
中介做爲房東的代理,與買房者直接接觸。並且中介還須要在真正交易前作其餘的事情(收取中介費,幫買房者check房源的真實性等等),所以該場景比較適合使用代理模式。
根據上面的代理模式的成員:
客戶端就是買房者
代理就是中介
真實主題就是房東
中介和房東都會實現收錢的方法,咱們能夠定義一個抽象主題類,它有一個公共接口是收錢的方法。
首先咱們定義一下房東和代理須要實現的接口PaymentInterface
(在類圖裏面是繼承某個共同對象,我我的比較習慣用接口來作)。
//================== PaymentInterface.h ==================
@protocol PaymentInterface <NSObject>
- (void)getPayment:(double)money;
@end
複製代碼
這個接口聲明瞭中介和房東都須要實現的方法getPayment:
接着咱們聲明代理類HouseProxy
:
//================== HouseProxy.h ==================
@interface HouseProxy : NSObject<PaymentInterface>
@end
//================== HouseProxy.m ==================
const float agentFeeRatio = 0.35;
@interface HouseProxy()
@property (nonatomic, copy) HouseOwner *houseOwner;
@end
@implementation HouseProxy
- (void)getPayment:(double)money{
double agentFee = agentFeeRatio * money;
NSLog(@"Proxy get payment : %.2lf",agentFee);
[self.houseOwner getPayment:(money - agentFee)];
}
- (HouseOwner *)houseOwner{
if (!_houseOwner) {
_houseOwner = [[HouseOwner alloc] init];
}
return _houseOwner;
}
@end
複製代碼
在HouseProxy
裏面,持有了房東,也就是被代理者的實例。而後在的getPayment:
方法裏,調用了房東實例的getPayment:
方法。並且咱們能夠看到,在調用房東實例的getPayment:
方法,代理先拿到了中介費(中介費比率agentFeeRatio
定義爲0.35,即中介費的比例佔35%)。
這裏面除了房東實例的getPayment:
方法以外的一些操做就是代理存在的意義:它能夠在真正被代理對象作事情以前,以後作一些其餘額外的事情。好比相似AOP編程同樣,定義相似的before***Method
或是after**Method
方法等等。
最後咱們看一下房東是如何實現getPayment:
方法的:
//================== HouseOwner.h ==================
@interface HouseOwner : NSObject<PaymentInterface>
@end
//================== HouseOwner.m ==================
@implementation HouseOwner
- (void)getPayment:(double)money{
NSLog(@"House owner get payment : %.2lf",money);
}
@end
複製代碼
房東類HouseOwner
按照本身的方式實現了getPayment:
方法。
不少時候被代理者(委託者)能夠徹底按照本身的方式去作事情,而把一些額外的事情交給代理來作,這樣能夠保持原有類的功能的純粹性,符合開閉原則。
下面咱們看一下客戶端的使用以及打印出來的結果:
客戶端代碼:
//================== client.m ==================
HouseProxy *proxy = [[HouseProxy alloc] init];
[proxy getPayment:100];
複製代碼
上面的客戶端支付給了中介100元。
下面咱們看一下打印結果:
Proxy get payment : 35.00
House owner get payment : 65.00
複製代碼
和預想的同樣,中介費收取了35%的中介費,剩下的交給了房東。
從UML類圖中咱們能夠看出,在這裏沒有使用抽象主題對象,而是用一個接口來分別讓中介和房東實現。
JDKDynamicAopProxy
是對JDK的動態代理進行了封裝裝飾模式(Decorator Pattern) :不改變原有對象的前提下,動態地給一個對象增長一些額外的功能。
裝飾者模式一共有四個成員:
模擬沙拉的製做:沙拉由沙拉底和醬汁兩個部分組成,不一樣的沙拉底和醬汁搭配能夠組成不一樣的沙拉。
沙拉底 | 價格 |
---|---|
蔬菜 | 5 |
雞肉 | 10 |
牛肉 | 16 |
醬汁 | 價格 |
---|---|
醋汁 | 2 |
花生醬 | 4 |
藍莓醬 | 6 |
注意:同一份沙拉底能夠搭配多鍾醬汁,並且醬汁的份數也能夠不止一份。
由於選擇一個沙拉底以後,能夠隨意添加不一樣份數和種類的醬汁,也就是在原有的沙拉對象增長新的對象,因此比較適合用裝飾者模式來設計:醬汁至關於裝飾者,而沙拉底則是被裝飾的構件。
下面咱們用代碼看一下如何實現該場景。
首先咱們定義 抽象構件,也就是沙拉類的基類Salad
:
//================== Salad.h ==================
@interface Salad : NSObject
- (NSString *)getDescription;
- (double)price;
@end
複製代碼
getDescription
和price
方法用來描述當前沙拉的配置以及價格(由於隨着裝飾者的裝飾,這兩個數據會一直變化)。
下面咱們再聲明裝飾者的基類SauceDecorator
。按照裝飾者設計模式類圖,該類是繼承於沙拉類的:
//================== SauceDecorator.h ==================
@interface SauceDecorator : Salad
@property (nonatomic, strong) Salad *salad;
- (instancetype)initWithSalad:(Salad *)salad;
@end
//================== SauceDecorator.m ==================
@implementation SauceDecorator
- (instancetype)initWithSalad:(Salad *)salad{
self = [super init];
if (self) {
self.salad = salad;
}
return self;
}
@end
複製代碼
在裝飾者的構造方法裏面傳入
Salad
類的實例,並將它保存下來,目的是爲了在裝飾它的時候用到。
如今抽象構件和裝飾者的基類都建立好了,下面咱們建立具體構件和具體裝飾者。首先咱們建立具體構件:
蔬菜沙拉VegetableSalad
:
//================== VegetableSalad.h ==================
@interface VegetableSalad : Salad
@end
//================== VegetableSalad.m ==================
@implementation VegetableSalad
- (NSString *)getDescription{
return @"[Vegetable Salad]";
}
- (double)price{
return 5.0;
}
@end
複製代碼
首先
getDescription
方法返回的是蔬菜沙拉底的描述;而後price
方法返回的是它所對應的價格。
相似的,咱們繼續按照價格表來建立雞肉沙拉底和牛肉沙拉底:
雞肉沙拉底:
//================== ChickenSalad.h ==================
@interface ChickenSalad : Salad
@end
//================== ChickenSalad.m ==================
@implementation ChickenSalad
- (NSString *)getDescription{
return @"[Chicken Salad]";
}
- (double)price{
return 10.0;
}
@end
複製代碼
牛肉沙拉底:
//================== BeefSalad.h ==================
@interface BeefSalad : Salad
@end
//================== BeefSalad.m ==================
@implementation BeefSalad
- (NSString *)getDescription{
return @"[Beef Salad]";
}
- (double)price{
return 16.0;
}
@end
複製代碼
如今全部的被裝飾者建立好了,下面咱們按照醬汁的價格表來建立醬汁類(也就是具體裝飾者):
首先看一下醋汁VinegarSauceDecorator
:
//================== VinegarSauceDecorator.h ==================
@interface VinegarSauceDecorator : SauceDecorator
@end
//================== VinegarSauceDecorator.m ==================
@implementation VinegarSauceDecorator
- (NSString *)getDescription{
return [NSString stringWithFormat:@"%@ + vinegar sauce",[self.salad getDescription]];
}
- (double)price{
return [self.salad price] + 2.0;
}
@end
複製代碼
重寫了
getDescription
方法,並添加了本身的裝飾,即在原來的描述上增長了+ vinegar sauce
字符串。之因此能夠獲取到原有的描述,是由於在構造方法裏已經獲取了被裝飾者的對象(在裝飾者基類中定義的方法)。一樣地,價格也在原來的基礎上增長了本身的價格。
如今咱們知道了具體裝飾者的設計,以此類推,咱們看一下花生醬和藍莓醬類如何定義:
花生醬PeanutButterSauceDecorator
類:
//================== PeanutButterSauceDecorator.h ==================
@interface PeanutButterSauceDecorator : SauceDecorator
@end
//================== PeanutButterSauceDecorator.m ==================
@implementation PeanutButterSauceDecorator
- (NSString *)getDescription{
return [NSString stringWithFormat:@"%@ + peanut butter sauce",[self.salad getDescription]];
}
- (double)price{
return [self.salad price] + 4.0;
}
@end
複製代碼
藍莓醬類BlueBerrySauceDecorator
:
//================== BlueBerrySauceDecorator.h ==================
@interface BlueBerrySauceDecorator : SauceDecorator
@end
//================== BlueBerrySauceDecorator.m ==================
@implementation BlueBerrySauceDecorator
- (NSString *)getDescription{
return [NSString stringWithFormat:@"%@ + blueberry sauce",[self.salad getDescription]];
}
- (double)price{
return [self.salad price] + 6.0;
}
@end
複製代碼
OK,到如今全部的類已經定義好了,爲了驗證是否實現正確,下面用客戶端嘗試着搭配幾種不一樣的沙拉吧:
首先咱們看第一個搭配:
//================== client ==================
//vegetable salad add vinegar sauce
Salad *vegetableSalad = [[VegetableSalad alloc] init];
NSLog(@"%@",vegetableSalad);
vegetableSalad = [[VinegarSauceDecorator alloc] initWithSalad:vegetableSalad];
NSLog(@"%@",vegetableSalad);
複製代碼
第一次打印輸出:This salad is: [Vegetable Salad] and the price is: 5.00
第二次打印輸出:This salad is: [Vegetable Salad] + vinegar sauce and the price is: 7.00
上面代碼中,咱們首先建立了蔬菜底,而後再讓醋汁裝飾它(將蔬菜底的實例傳入醋汁裝飾者的構造方法中)。最後咱們打印這個蔬菜底對象,描述和價格和裝飾以前的確實發生了變化,說明咱們的代碼沒有問題。
接着咱們看第二個搭配:
//================== client ==================
//beef salad add two peanut butter sauce:
Salad *beefSalad = [[BeefSalad alloc] init];
NSLog(@"%@",beefSalad);
beefSalad = [[PeanutButterSauceDecorator alloc] initWithSalad:beefSalad];
NSLog(@"%@",beefSalad);
beefSalad = [[PeanutButterSauceDecorator alloc] initWithSalad:beefSalad];
NSLog(@"%@",beefSalad);
複製代碼
第一次打印輸出:[Beef Salad] and the price is: 16.00
第二次打印輸出:[Beef Salad] + peanut butter sauce and the price is: 20.00
第三次打印輸出:[Beef Salad] + peanut butter sauce + peanut butter sauce and the price is: 24.00
和上面的代碼實現相似,都是先建立沙拉底(此次是牛肉底),而後再添加調料。因爲是分兩次裝飾,因此要再寫一次花生醬的裝飾代碼。對比每次打印的結果和上面的價格表能夠看出輸出是正確的。
這個例子是加了兩次相同的醬汁,最後咱們看第三個搭配,加入的是不一樣的兩個醬汁:
//================== client ==================
//chiken salad add peanut butter sauce and blueberry sauce
Salad *chikenSalad = [[ChickenSalad alloc] init];
NSLog(@"%@",chikenSalad);
chikenSalad = [[PeanutButterSauceDecorator alloc] initWithSalad:chikenSalad];
NSLog(@"%@",chikenSalad);
chikenSalad = [[BlueBerrySauceDecorator alloc] initWithSalad:chikenSalad];
NSLog(@"%@",chikenSalad);
複製代碼
第一次打印輸出:[Chicken Salad] and the price is: 10.00
第二次打印輸出:[Chicken Salad] + peanut butter sauce and the price is: 14.00
第三次打印輸出:[Chicken Salad] + peanut butter sauce + blueberry sauce and the price is: 20.00
對比每次打印的結果和上面的價格表能夠看出輸出是正確的。
到這裏,該場景就模擬結束了。能夠試想一下,若是從此加了其餘的沙拉底和醬汁的話,只須要分別繼承Salad
類和SauceDecorator
類就能夠了,現有的代碼並不須要更改;並且通過不一樣組合能夠搭配出更多種類的沙拉。
下面咱們看一下該代碼實現對應的類圖。
BufferReader
繼承了Reader
,在BufferReader
的構造器中傳入了Reader
,實現了裝飾享元模式(Flyweight Pattern):運用共享技術複用大量細粒度的對象,下降程序內存的佔用,提升程序的性能。
定義解讀:
享元模式一共有三個成員:
這裏咱們使用《Objective-C 編程之道:iOS設計模式解析》裏的第21章使用的例子:在一個頁面展現數百個大小,位置不一樣的花的圖片,然而這些花的樣式只有6種。
看一下截圖:
因爲這裏咱們須要建立不少對象,而這些對象有能夠共享的內部狀態(6種圖片內容)以及不一樣的外部狀態(隨機的,數百個位置座標和圖片大小),所以比較適合使用享元模式來作。
根據上面提到的享元模式的成員:
UIImageView
,它能夠顯示圖片UIImageView
,由於後續咱們能夠直接添加更多其餘的屬性。下面咱們看一下用代碼如何實現:
首先咱們建立一個工廠,這個工廠能夠根據所傳入花的類型來返回花內部圖片對象,在這裏能夠直接使用原生的UIImage
對象,也就是圖片對象。並且這個工廠持有一個保存圖片對象的池子:
下面咱們看一下代碼是如何實現的:
//================== FlowerFactory.h ==================
typedef enum
{
kAnemone,
kCosmos,
kGerberas,
kHollyhock,
kJasmine,
kZinnia,
kTotalNumberOfFlowerTypes
} FlowerType;
@interface FlowerFactory : NSObject
- (FlowerImageView *) flowerImageWithType:(FlowerType)type
@end
//================== FlowerFactory.m ==================
@implementation FlowerFactory
{
NSMutableDictionary *_flowersPool;
}
- (FlowerImageView *) flowerImageWithType:(FlowerType)type
{
if (_flowersPool == nil){
_flowersPool = [[NSMutableDictionary alloc] initWithCapacity:kTotalNumberOfFlowerTypes];
}
//嘗試獲取傳入類型對應的花內部圖片對象
UIImage *flowerImage = [_flowersPool objectForKey:[NSNumber numberWithInt:type]];
//若是沒有對應類型的圖片,則生成一個
if (flowerImage == nil){
NSLog(@"create new flower image with type:%u",type);
switch (type){
case kAnemone:
flowerImage = [UIImage imageNamed:@"anemone.png"];
break;
case kCosmos:
flowerImage = [UIImage imageNamed:@"cosmos.png"];
break;
case kGerberas:
flowerImage = [UIImage imageNamed:@"gerberas.png"];
break;
case kHollyhock:
flowerImage = [UIImage imageNamed:@"hollyhock.png"];
break;
case kJasmine:
flowerImage = [UIImage imageNamed:@"jasmine.png"];
break;
case kZinnia:
flowerImage = [UIImage imageNamed:@"zinnia.png"];
break;
default:
flowerImage = nil;
break;
}
[_flowersPool setObject:flowerImage forKey:[NSNumber numberWithInt:type]];
}else{
//若是有對應類型的圖片,則直接使用
NSLog(@"reuse flower image with type:%u",type);
}
//建立花對象,將上面拿到的花內部圖片對象賦值並返回
FlowerImageView *flowerImageView = [[FlowerImageView alloc] initWithImage:flowerImage];
return flowerImageView;
}
複製代碼
- 在這個工廠類裏面定義了六中圖片的類型
- 該工廠類持有
_flowersPool
私有成員變量,保存新建立過的圖片。flowerImageWithType:
的實現:結合了_flowersPool
:當_flowersPool
沒有對應的圖片時,新建立圖片並返回;不然直接從_flowersPool
獲取對應的圖片並返回。
接着咱們定義這些花對象FlowerImageView
:
//================== FlowerImageView.h ==================
@interface FlowerImageView : UIImageView
@end
//================== FlowerImageView.m ==================
@implementation FlowerImageView
@end
複製代碼
在這裏面其實也能夠直接使用
UIImageView
,之因此建立一個子類是爲了後面能夠更好地擴展這些花獨有的一些屬性。注意一下花對象和花內部圖片對象的區別:花對象
FlowerImageView
是包含花內部圖片對象的UIImage
。由於在Objective-C裏面,UIImage
是FlowerImageView
所繼承的UIImageView
的一個屬性,因此在這裏FlowerImageView
就直接包含了UIImage
。
下面咱們來看一下客戶端如何使用FlowerFactory
和FlowerImageView
這兩個類:
//================== client ==================
//首先建造一個生產花內部圖片對象的工廠
FlowerFactory *factory = [[FlowerFactory alloc] init];
for (int i = 0; i < 500; ++i)
{
//隨機傳入一個花的類型,讓工廠返回該類型對應花類型的花對象
FlowerType flowerType = arc4random() % kTotalNumberOfFlowerTypes;
FlowerImageView *flowerImageView = [factory flowerImageWithType:flowerType];
// 建立花對象的外部屬性值(隨機的位置和大小)
CGRect screenBounds = [[UIScreen mainScreen] bounds];
CGFloat x = (arc4random() % (NSInteger)screenBounds.size.width);
CGFloat y = (arc4random() % (NSInteger)screenBounds.size.height);
NSInteger minSize = 10;
NSInteger maxSize = 50;
CGFloat size = (arc4random() % (maxSize - minSize + 1)) + minSize;
//將位置和大小賦予花對象
flowerImageView.frame = CGRectMake(x, y, size, size);
//展現這個花對象
[self.view addSubview:flowerImageView];
}
複製代碼
上面代碼裏面是生成了500朵位置和大小都是隨機的花內部圖片對象。這500朵花最主要的區別仍是它們的位置和大小;而它們使用的花的圖片對象只有6個,所以能夠用專門的Factory
來生成和管理這些少數的花內部圖片對象,從工廠的打印咱們能夠看出來:
create new flower image with type:1
create new flower image with type:3
create new flower image with type:4
reuse flower image with type:3
create new flower image with type:5
create new flower image with type:2
create new flower image with type:0
reuse flower image with type:5
reuse flower image with type:5
reuse flower image with type:4
reuse flower image with type:1
reuse flower image with type:3
reuse flower image with type:4
reuse flower image with type:0
複製代碼
從上面的打印結果能夠看出,在六種圖片都建立好之後,再獲取時就直接拿生成過的圖片了,在必定程度上減小了內存的開銷。
下面咱們來看一下該代碼示例對應的UML類圖。
這裏須要注意的是
- 工廠和花對象是組合關係:
FlowerFactroy
生成了多個FlowerImageView
對象,也就是花的內部圖片對象,兩者的關係屬於強關係,由於在該例子中兩者若是分離而獨立存在都將會失去意義,因此在UML類圖中用了組合的關係(實心菱形)。- 抽象享元類是
UIImageView
,它的一個內部對象是UIImage
(這兩個都是Objective-C原生的關於圖片的類)。- 客戶端依賴的對象是工廠對象和花對象,而對花的內部圖片對象
UIImage
能夠一無所知,由於它是被FlowerFactroy
建立並被FlowerImageView
所持有的。(可是由於UIImage
是FlowerImageView
的一個外部能夠引用的屬性,因此在這裏客戶端仍是能夠訪問到UIImage
,這是Objective-C原生的實現。後面咱們在用享元模式的時候能夠不將內部屬性暴露出來)
使用享元模式須要分離出內部狀態和外部狀態,這使得程序的邏輯複雜化。
對象在緩衝池中的複用須要考慮線程問題。
UITableViewCell
的複用池就是使用享元模式的一個例子。Integer
類的valueOf
方法,若是傳入的值的區間在[IntegerCache.low,IntegerCache.high]
中的話,則直接從緩存裏獲取;不然就建立一個新的Integer
。到這裏設計模式中的結構型模式就介紹完了,讀者能夠結合UML類圖和demo的代碼來理解每一個設計模式的特色和相互之間的區別,但願讀者能夠有所收穫。
另外,本篇博客的代碼和類圖都保存在個人GitHub庫中:object-oriented-design chapter 2.2
該系列前面的兩篇文章:
本篇的下一篇是面向對象系列的第四篇,講解的是面向對象設計模式中的行爲型模式。
本篇已同步到我的博客:面向對象設計的設計模式(二):結構型模式(附 Demo 及 UML 類圖)
筆者在近期開通了我的公衆號,主要分享編程,讀書筆記,思考類的文章。
由於公衆號天天發佈的消息數有限制,因此到目前爲止尚未將全部過去的精選文章都發布在公衆號上,後續會逐步發佈的。
並且由於各大博客平臺的各類限制,後面還會在公衆號上發佈一些短小精幹,以小見大的乾貨文章哦~
掃下方的公衆號二維碼並點擊關注,期待與您的共同成長~