什么是策略模式
策略模式定義了算法族,分別封裝起來,讓它們之間可以互相替換,此模式讓算法的變化獨(dú)立于使用算法的客戶。——《Head First 設(shè)計模式》
這里引用了《Head First 設(shè)計模式》里的定義。其中算法族是指一系列算法,可以把算法理解為對象的行為(方法)。
這里用到了一個設(shè)計原則:
找出應(yīng)用中可能需要變化之處,把它們獨(dú)立出來,不要和那些不需要變化的代碼混在一起。
這是一個讓我耳目一新的思想,我的固有思維里總是把不變的東西封裝起來,比如基類的使用(當(dāng)然,這些變化里其實是有不變的——相同方法的不同實現(xiàn))。而這種思想是“把會變化的部分取出來并封裝起來,以便以后可以輕易地改動或擴(kuò)充此部分,而不影響不需要變化的其它部分”。在接下來的實踐中我們會更加深刻地體會這種思想。
需求場景
現(xiàn)有一個野鴨類MallardDuck
,野鴨會飛 fly()
,會叫 quack()
。很快有了新的需求,我們要實現(xiàn)一個家鴨(飼養(yǎng)的鴨子)的類DomesticDuck
,家鴨不會飛fly()
,叫聲quack()
(嘎嘎叫)也和野鴨MallardDuck
(咕咕叫)不同。
通常情況下利用面向?qū)ο蟮乃枷耄鶕?jù)鴨子的共性我們會抽出一個鴨子基類Duck
。然后繼承創(chuàng)建野鴨類MallardDuck
和家鴨類DomesticDuck
,并在子類里分別重寫飛的方法fly()
和叫的方法quack()
。
但是,這樣實現(xiàn)方式有一個問題,我們知道需求總是在不斷變化的,產(chǎn)品經(jīng)理某一天突發(fā)奇想要很多其它類別的鴨子,并且鴨子的飛行的方式
fly()
和叫的方式quack()
可能一樣,也可能不一樣。如果我們總是在子類里重寫方法,就沒辦法對相同的方法進(jìn)行重用,而且也不能方便地對每個類的方法進(jìn)行修改。
所以,我們應(yīng)該把鴨子的行為從鴨子類Duck
里剝離出來單獨(dú)封裝。
解決方案
這里有兩種解決方案。
實現(xiàn)繼承
- 方案一:將行為封裝為類,通過繼承在子類重寫行為方法,我們將之稱為實現(xiàn)繼承。
- 具體實現(xiàn):創(chuàng)建
FlyBehavior
類和QuackBehavior
類,分別在類中實現(xiàn)fly()
和quack()
,然后繼承創(chuàng)建子類FlyWithWings
和FlyNoWay
。對QuackBehavior
做同樣的處理,GuGuQuack
和GaGaQuack
。
這樣似乎也能解決問題,但是我們根據(jù)針對接口編程,而不是針對實現(xiàn)編程的設(shè)計原則采用第二種解決方案。“針對接口編程”的意思是“針對超類型編程”,即“變量的聲明類型應(yīng)該是超類型,通常是一個抽象類或者是一個接口”。利用多態(tài)機(jī)制,只要是實現(xiàn)了該接口的對象都可以指定給這個變量,運(yùn)行時根據(jù)實際賦予的變量執(zhí)行真正的行為。實現(xiàn)和接口分離,而不是死綁在一起。
接口繼承
- 方案二:把行為設(shè)計成接口,再讓具體的行為類去實現(xiàn)對應(yīng)的方法。
- 具體實現(xiàn):在 Objective-C 中,我們可以用協(xié)議
Protocal
來定義飛行為<FlyBehavior>
和叫行為<QuackBehavior>
,讓具體的行為類(FlyWithWings<FlyBehavior>
、FlyNoWay<FlyBehavior>
、GuGuQuack<QuackBehavior>
、GaGaQuack<QuackBehavior>
)去實現(xiàn)相應(yīng)的方法。
代碼實現(xiàn)
下面給出方案二的代碼實現(xiàn)。
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 協(xié)議
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
整合調(diào)用
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 設(shè)計模式(Java)》