什么是策略模式
策略模式定義了算法族,分別封裝起來,讓它們之間可以互相替換,此模式讓算法的變化獨立于使用算法的客戶?!禜ead First 設計模式》
這里引用了《Head First 設計模式》里的定義。其中算法族是指一系列算法,可以把算法理解為對象的行為(方法)。
這里用到了一個設計原則:
找出應用中可能需要變化之處,把它們獨立出來,不要和那些不需要變化的代碼混在一起。
這是一個讓我耳目一新的思想,我的固有思維里總是把不變的東西封裝起來,比如基類的使用(當然,這些變化里其實是有不變的——相同方法的不同實現)。而這種思想是“把會變化的部分取出來并封裝起來,以便以后可以輕易地改動或擴充此部分,而不影響不需要變化的其它部分”。在接下來的實踐中我們會更加深刻地體會這種思想。
需求場景
現有一個野鴨類MallardDuck
,野鴨會飛 fly()
,會叫 quack()
。很快有了新的需求,我們要實現一個家鴨(飼養的鴨子)的類DomesticDuck
,家鴨不會飛fly()
,叫聲quack()
(嘎嘎叫)也和野鴨MallardDuck
(咕咕叫)不同。
通常情況下利用面向對象的思想,根據鴨子的共性我們會抽出一個鴨子基類Duck
。然后繼承創建野鴨類MallardDuck
和家鴨類DomesticDuck
,并在子類里分別重寫飛的方法fly()
和叫的方法quack()
。
但是,這樣實現方式有一個問題,我們知道需求總是在不斷變化的,產品經理某一天突發奇想要很多其它類別的鴨子,并且鴨子的飛行的方式
fly()
和叫的方式quack()
可能一樣,也可能不一樣。如果我們總是在子類里重寫方法,就沒辦法對相同的方法進行重用,而且也不能方便地對每個類的方法進行修改。
所以,我們應該把鴨子的行為從鴨子類Duck
里剝離出來單獨封裝。
解決方案
這里有兩種解決方案。
實現繼承
- 方案一:將行為封裝為類,通過繼承在子類重寫行為方法,我們將之稱為實現繼承。
- 具體實現:創建
FlyBehavior
類和QuackBehavior
類,分別在類中實現fly()
和quack()
,然后繼承創建子類FlyWithWings
和FlyNoWay
。對QuackBehavior
做同樣的處理,GuGuQuack
和GaGaQuack
。
這樣似乎也能解決問題,但是我們根據針對接口編程,而不是針對實現編程的設計原則采用第二種解決方案。“針對接口編程”的意思是“針對超類型編程”,即“變量的聲明類型應該是超類型,通常是一個抽象類或者是一個接口”。利用多態機制,只要是實現了該接口的對象都可以指定給這個變量,運行時根據實際賦予的變量執行真正的行為。實現和接口分離,而不是死綁在一起。
接口繼承
- 方案二:把行為設計成接口,再讓具體的行為類去實現對應的方法。
- 具體實現:在 Objective-C 中,我們可以用協議
Protocal
來定義飛行為<FlyBehavior>
和叫行為<QuackBehavior>
,讓具體的行為類(FlyWithWings<FlyBehavior>
、FlyNoWay<FlyBehavior>
、GuGuQuack<QuackBehavior>
、GaGaQuack<QuackBehavior>
)去實現相應的方法。
代碼實現
下面給出方案二的代碼實現。
Duck 類
Duck.h 文件
#import <Foundation/Foundation.h>
#import "FlyBehavior.h"
#import "QuackBehavior.h"
@interface Duck : NSObject
@property (strong, nonatomic)id<FlyBehavior> flyBehavior;
@property (strong, nonatomic)id<QuackBehavior> quackBehavior;
- (void)performFly;
- (void)performQuack;
@end
Duck.m 文件
#import "Duck.h"
@implementation Duck
- (void)performFly{
[self.flyBehavior fly];
}
- (void)performQuack{
[self.quackBehavior quack];
}
@end
MallardDuck 類
MallardDuck.h 文件
#import "Duck.h"
@interface MallardDuck : Duck
@end
MallardDuck.m 文件
#import "MallardDuck.h"
#import "FlyWithWings.h"
#import "GuGuQuack.h"
@implementation MallardDuck
- (instancetype)init{
if (self = [super init]) {
self.flyBehavior = [[FlyWithWings alloc]init];
self.quackBehavior = [[GuGuQuack alloc]init];
NSLog(@"I'm a mallardDuck");
}
return self;
}
@end
DomesticDuck 類
DomesticDuck.h 文件
#import "Duck.h"
@interface DomesticDuck : Duck
@end
DomesticDuck.m 文件
#import "DomesticDuck.h"
#import "FlyNoWay.h"
#import "GaGaQuack.h"
@implementation DomesticDuck
- (instancetype)init{
if (self = [super init]) {
self.quackBehavior = [[GaGaQuack alloc]init];
self.flyBehavior = [[FlyNoWay alloc]init];
NSLog(@"I'm a DomesticDuck");
}
return self;
}
@end
FlyBehavior 協議
FlyBehavior.h 文件
#import <Foundation/Foundation.h>
@protocol FlyBehavior <NSObject>
@required
- (void)fly;
@end
FlyNoWay 類
FlyNoWay.h 文件
#import <Foundation/Foundation.h>
#import "FlyBehavior.h"
@interface FlyNoWay : NSObject <FlyBehavior>
- (void)fly;
@end
FlyNoWay.m 文件
#import "FlyNoWay.h"
@implementation FlyNoWay
- (void)fly{
NSLog(@"I can't fly");
}
@end
FlyWithWings 類
FlyWithWings.h 文件
#import <Foundation/Foundation.h>
#import "FlyBehavior.h"
@interface FlyWithWings : NSObject <FlyBehavior>
- (void)fly;
@end
FlyWithWings.m 文件
#import "FlyWithWings.h"
@implementation FlyWithWings
- (void)fly{
NSLog(@"I can fly");
}
@end
整合調用
main.m 文件
#import <Foundation/Foundation.h>
#import "MallardDuck.h"
#import "DomesticDuck.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
Duck *mallardDuck = [[MallardDuck alloc]init];
[mallardDuck performFly];
[mallardDuck performQuack];
Duck *domesticDuck = [[DomesticDuck alloc]init];
[domesticDuck performFly];
[domesticDuck performQuack];
}
return 0;
}
源代碼地址
Github地址: https://github.com/Zentopia/DesignPatterns
參考資料
- 《Head First 設計模式(Java)》