引言
這是在項目開發過程中的需求:
用戶Zing_LYF在photo2頻道中發布了內容為“是不是真的”的感言,除了頭部的展示內容不同,其他都一樣。
(1)在個人中心,感言頭部顯示該感言所屬頻道的頻道icon和頻道名稱,點擊icon,跳轉到頻道詳情
(2)在頻道詳情,感言頭部展示發布此感言的用戶頭像和用戶名,點擊頭像,跳轉該用戶的個人中心
一般的思路:
cell的布局一模一樣,但是在給cell綁定感言模型的時候,同時傳一個模塊參數表示是個人中心 還是 頻道詳情,然后在cell里面再通過if else 判斷不同的模塊,處理不同的邏輯。
問題
隨著以后模塊的繼續增加,cell中會有很多if else 的邏輯判斷,顯得邏輯很混亂,代碼很冗余。
思考
我們發現這兩個模塊的感言數據是一樣的,但是部分的展示模式隨著模塊的不同而不同,這幾引出我們今天的設計模式——策略設計模式
策略設計模式
1.概念
策略模式定義了一系列的算法,并將每一個算法封裝起來,而且使它們還可以相互替換。策略模式讓算法獨立于使用它的客戶而獨立變化。
2.組成
- 抽象策略角色: 策略類,通常由一個接口或者抽象類實現。
- 具體策略角色:包裝了相關的算法和行為。
- 環境角色:持有一個策略類的引用,最終給客戶端調用。
3.概念
Context(應用場景):
1、需要使用ConcreteStrategy提供的算法。
2、 內部維護一個Strategy的實例。
3、 負責動態設置運行時Strategy具體的實現算法。
4、負責跟Strategy之間的交互和數據傳遞。
Strategy(抽象策略類):
1、 定義了一個公共接口,各種不同的算法以不同的方式實現這個接口,Context使用這個接口調用不同的算法,一般使用接口或抽象類實現。
ConcreteStrategy(具體策略類):
2、 實現了Strategy定義的接口,提供具體的算法實現。
4.UML類圖
5.使用
1.抽象策略類:SenseHeaderDisplayStrategy(感言頭部展示策略)
@interface SenseHeaderDisplayStrategy : NSObject<SenseHeaderDisplayStrategy>
@end
2.抽象策略類定義的一系列接口:
/**
頭部視圖策略接口的定義
*/
@protocol SenseHeaderDisplayStrategy <NSObject>
@optional
/**
獲取頭像URL
@param sense sense
@return 頭像URL
*/
- (NSString *)getIconUrlWithSense:(ZTMSense *)sense;
/**
獲取標題文本
@param sense sense
@return 標題文本
*/
- (NSString *)getTitleTextWithSense:(ZTMSense *)sense;
/**
點擊頭像邏輯跳轉
@param sense sense
@param delegate 代理
*/
- (void)triggerIconClickWithSense:(ZTMSense *)sense delegate:(id<SenseCellDelegate>)delegate;
@end
3.具體策略類:SenseHeaderDisplay4Channel(頭部展示頻道信息的策略類)
實現具體的接口:
#pragma mark - SenseHeaderDisplayStrategy
- (NSString *)getIconUrlWithSense:(ZTMSense *)sense{
//頻道icon
return [ZingFileManager getPortrait:sense.channel.icon];
}
- (NSString *)getTitleTextWithSense:(ZTMSense *)sense {
//頻道名稱
return sense.channel.name;
}
- (void)triggerIconClickWithSense:(ZTMSense *)sense delegate:(id<SenseCellDelegate>)delegate {
//跳轉頻道詳情
[self onNavigationToChannelWithSense:sense];
}
- (void)onNavigationToChannelWithSense:(ZTMSense *)sense {
[[ZingManager getCurrentViewController] gotoChannelWithId:sense.channelId];
}
4.具體策略類:SenseHeaderDisplay4Person(頭部展示個人信息的策略類)
實現具體的接口:
#pragma mark - SenseHeaderDisplayStrategy
- (NSString *)getIconUrlWithSense:(ZTMSense *)sense {
//用戶頭像
return [ZingFileManager getPortrait:sense.user.avatar];
}
- (NSString *)getTitleTextWithSense:(ZTMSense *)sense {
//用戶名
return sense.user.userName;
}
- (void)triggerIconClickWithSense:(ZTMSense *)sense delegate:(id<SenseCellDelegate>)delegate {
//跳轉用戶個人中心
[self onNavigationToPersonWithUser:sense.user];
}
- (void)onNavigationToPersonWithUser:(ZTMUserDescription *)user {
[[ZingManager getCurrentViewController] gotoProfileWithUserId:user.id_p];
}
5.在對應的模塊實例對應的策略類,讓cell持有對應策略類,調用對應具體策略類的實現
(1)頻道詳情的頁面,調用展示用戶信息的策略類
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
ZTMSenseLayout *layout;
if (indexPath.section == 0) {
layout = self.draftLayouts[indexPath.row];
}else{
layout = self.senseLayouts[indexPath.row];
}
SenseCell *cell = [SenseCell cellWithTableView:tableView delegate:self type:layout.sense.content.mediaType headerDisplayStrategy:[SenseHeaderDisplay4Personal defaultDisplayStrategy]];
[layout setIndexPath:indexPath];
if (layout.tableView != tableView) layout.tableView = tableView;
[cell layoutSubviewsWithLayout:layout];
return cell;
}
(2)個人中心的頁面,調用展示頻道信息的策略類
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
ZTMSenseLayout *layout;
if (indexPath.section == 0) {
layout = self.draftLayouts[indexPath.row];
} else {
layout = self.senseLayouts[indexPath.row];
}
SenseCell *cell = [SenseCell cellWithTableView:tableView delegate:self type:layout.sense.content.mediaType headerDisplayStrategy:[SenseHeaderDisplay4Channel defaultDisplayStrategy]];
[layout setIndexPath:indexPath];
if (layout.tableView != tableView) layout.tableView = tableView;
[cell layoutSubviewsWithLayout:layout];
return cell;
}
6.在cell布局的時候調用策略類
/** 頭部顯示策略 */
@property (nonatomic, strong) SenseHeaderDisplayStrategy *headerDisplayStrategy;
/**
布局
@param layout 布局
*/
- (void)layoutSubviewsWithLayout:(ZTMSenseLayout *)layout {
[super layoutSubviewsWithLayout:layout];
//設置頭像,調用策略類
[self.avatar_imageView sd_setImageWithURL:[ZingFileManager getPortraitURLWithString:[self.headerDisplayStrategy getIconUrlWithSense:layout.sense]] placeholderImage:[UIImage avatarPlaceholder] options:SDWebImageLowPriority];
//設置名字,調用策略類
_userName_label.text = [_headerDisplayStrategy getTitleTextWithSense:layout.sense];
}
[self.avatar_imageView addTapGestureBlock:^{
//點擊頭像,調用策略類
[weak_self.headerDisplayStrategy triggerIconClickWithSense:weak_self.sense delegate:weak_self.delegate];
}];
6.優缺點
優點:
1、 策略模式提供了管理相關的算法族的辦法。策略類的等級結構定義了一個算法或行為族。恰當使用繼承可以把公共的代碼轉移到父類里面,從而避免重復的代碼。
2、 策略模式提供了可以替換繼承關系的辦法。繼承可以處理多種算法或行為。如果不是用策略模式,那么使用算法或行為的環境類就可能會有一些子類,每一個子類提供一個不同的算法或行為。但是,這樣一來算法或行為的使用者就和算法或行為本身混在一起。決定使用哪一種算法或采取哪一種行為的邏輯就和算法或行為的邏輯混合在一起,從而不可能再獨立演化。繼承使得動態改變算法或行為變得不可能。
3、 使用策略模式可以避免使用多重條件轉移語句。多重轉移語句不易維護,它把采取哪一種算法或采取哪一種行為的邏輯與算法或行為的邏輯混合在一起,統統列在一個多重轉移語句里面,比使用繼承的辦法還要原始和落后。
缺點:
1、客戶端必須知道所有的策略類,并自行決定使用哪一個策略類。這就意味著客戶端必須理解這些算法的區別,以便適時選擇恰當的算法類。換言之,策略模式只適用于客戶端知道所有的算法或行為的情況。
2、 策略模式造成很多的策略類,每個具體策略類都會產生一個新類。有時候可以通過把依賴于環境的狀態保存到客戶端里面,而將策略類設計成可共享的,這樣策略類實例可以被不同客戶端使用。