1. 簡介
策略(Strategy)模式定義了一系列的算法,并將每一個算法封裝起來,而且使它們還可以相互替換。策略模式讓算法獨立于使用它的客戶而獨立變化。策略模式是對算法的包裝,是把使用算法的責任和算法本身分割開來,委派給不同
的對象管理。看到策略模式的時候有的時候跟簡單工廠相比較,其實有很大的迷惑性,都是繼承多態感覺沒有太大的差異性,簡單工廠模式是對對象的管理,策略模式是對行為的封裝。可以先簡單的看一下結構圖:
之前簡單工廠是通過銀行卡作為例子的簡單工廠將不同的銀行卡抽象出來,如果在策略模式中我們可以將每張銀行卡的購物,吃飯,住房。。作為一個簡單的消費策略抽象出來,也可以以操作系統類比,Windows,OS X,Linux可以作為簡單的對象抽象,系統中都是有默認軟件的,我們不需要管軟件的安裝,如果沒有軟件的話我們就需要自己下載,可以將軟件的安裝作為一個策略封裝起來。
抽象策略角色: 策略類,通常由一個接口或者抽象類實現。
具體策略角色:包裝了相關的算法和行為。
環境角色:持有一個策略類的引用,最終給客戶端調用。
2. 應用場景
1、 多個類只區別在表現行為不同,可以使用Strategy模式,在運行時動態選擇具體要執行的行為。
2、 需要在不同情況下使用不同的策略(算法),或者策略還可能在未來用其它方式來實現。
3、 對客戶隱藏具體策略(算法)的實現細節,彼此完全獨立。
3. 代碼演示
3.1 C語言實現
// 統一的文件接口
typedef struct _MoviePlay
{
struct _CommMoviePlay* pCommMoviePlay;
}MoviePlay;
typedef struct _CommMoviePlay
{
HANDLE hFile;
void (*play)(HANDLE hFile);
}CommMoviePlay;
void play_avi_file(HANDLE hFile)
{
printf("play avi file!\n");
}
void play_rmvb_file(HANDLE hFile)
{
printf("play rmvb file!\n");
}
void play_mpeg_file(HANDLE hFile)
{
printf("play mpeg file!\n");
}
void play_movie_file(struct MoviePlay* pMoviePlay)
{
CommMoviePlay* pCommMoviePlay;
assert(NULL != pMoviePlay);
pCommMoviePlay = pMoviePlay->pCommMoviePlay;
pCommMoviePlay->play(pCommMoviePlay->hFile);
}
3.2 C++語言實現
Strategy.h
#include <iostream>
#include <string>
#include <memory>
using namespace std;
//strategy抽象類,用作接口
class Strategy
{
public:
virtual string substitute(string str)=0;
virtual ~Strategy()
{
cout<<" in the destructor of Strategy"<<endl;
}
};
class ChineseStrategy:public Strategy
{
public:
string substitute(string str)
{
int index=str.find("520");
string tempstr=str.replace(index,3,"我愛你");
return tempstr;
}
~ChineseStrategy()
{
cout<<"in the destructor of ChineseStrategy"<<endl;
}
};
class EnglishStrategy:public Strategy
{
public:
string substitute(string str)
{
int index=str.find("520");
string tempstr=str.replace(index,3,"i love ou");
return tempstr;
}
~EnglishStrategy()
{
cout<<" in the destructor of ChineseStrategy"<<endl;
}
};
//Context類
class Translator
{
private:
auto_ptr<Strategy> strategy;
//在客戶代碼中加入算法(stategy)類型的指針。
public:
~Translator()
{
cout<<" in the destructor of Translator"<<endl;
}
void set_strategy(auto_ptr<Strategy> strategy)
{
this->strategy=strategy;
}
string translate(string str)
{
if(0==strategy.get())
return "";
return strategy->substitute(str);
}
};
Strategy.cpp
#include "Strategy.h"
int main(int argc, char *argv)
{
string str("321520");
Translator *translator=new Translator;
//未指定strategy的時候
cout<<"No Strategy"<<endl;
translator->translate(str);
cout<<"---------------"<<endl;
//翻譯成中文
auto_ptr<Strategy> s1(new ChineseStrategy);
translator->set_strategy(s1);
cout<<"Chinese Strategy"<<endl;
cout<<translator->translate(str)<<endl;
cout<<"---------------"<<endl;
//翻譯成英文
auto_ptr<Strategy> s2(new EnglishStrategy);
translator->set_strategy(s2);
cout<<"English Strategy"<<endl;
cout<<translator->translate(str)<<endl;
cout<<"----------------"<<endl;
delete translator;
return 0;
}
3.3 OC實現:安裝軟件
Strategy的抽象類:
@interface SoftWareStrategy : NSObject
-(void)installStrategy;
@end
繼承Strategy的Xcode的策略類:
@implementation XcodeStrategy
-(void)installStrategy{
NSLog(@"Xcode安裝成功");
}
@end
繼承Strategy的QQ的策略類:
@implementation QQStrategy
-(void)installStrategy{
NSLog(@"QQ安裝成功");
}
@end
typedef NS_OPTIONS(NSInteger, StrategyType){
StrategyXcode,
strategyQQ
};
@interface SoftWareContext : NSObject
-(instancetype)initWithStrategyType:(StrategyType)strategyType;
-(void)installResult;
@end
@interface SoftWareContext()
@property (strong,nonatomic) SoftWareStrategy *strategy;
@end
@implementation SoftWareContext
-(instancetype)initWithStrategyType:(StrategyType)strategyType{
self=[super init];
if (self) {
switch (strategyType) {
case StrategyXcode:
self.strategy=[[XcodeStrategy alloc]init];
break;
case strategyQQ:
self.strategy=[[QQStrategy alloc]init];
break;
}
}
return self;
}
-(void)installResult{
[self.strategy installStrategy];
}
@end
最終的調用:
SoftWareContext *context=[[SoftWareContext alloc]initWithStrategyType:StrategyXcode];
[context installResult];
3.4 OC實現:播放器
不同的第三方播放器只區別在播放、暫停、停止等一系列方法的實現和調用上的不同。我們的需求就是在未來可能會使用不同的播放器,而這些對客戶來說應該是隱藏的,不關心具體細節的,彼此完全獨立的。所以,完全可以通過策略模式來解決我們的需求。下面我們看其代碼實現。
(1)策略模式的核心就是對算法變化的封裝。
定義一個通用算法協議,讓每個算法遵守其規則。
@protocol LHPlayerProtocol <NSObject>
/**
* Player開啟視頻
*
* @return 描述(只為舉例需要)
*/
- (NSString *)lh_play;
/**
* Player暫停視頻
*
* @return 描述(只為舉例需要)
*/
- (NSString *)lh_pause;
/**
* Player停止播放
*
* @return 描述(只為舉例需要)
*/
- (NSString *)lh_stop;
AVPlayer的算法封裝
#import <Foundation/Foundation.h>
#import "LHPlayerProtocol.h"
@interface LHAVPlayer : NSObject<LHPlayerProtocol>
@end
#import "LHAVPlayer.h"
#import "AVPlayer.h"
@interface LHAVPlayer ()
{
id<AVPlayerProtocol> player;// AVPlayer播放器自身的協議
}
@end
@implementation LHAVPlayer
- (instancetype)init
{
self = [super init];
if (self) {
player = [[AVPlayer alloc] init];// 初始化AVPlayer播放器對象
}
return self;
}
// 播放
- (NSString *)lh_play{
return [player a_play];
}
// 暫停
- (NSString *)lh_pause{
return [player a_pause];
}
// 停止
- (NSString *)lh_stop{
return [player a_stop];
}
- (void)dealloc
{
player = nil;
}
@end
IJKPlayer的算法封裝
#import <Foundation/Foundation.h>
#import "LHPlayerProtocol.h"
@interface LHIJKPlayer : NSObject<LHPlayerProtocol>
@end
#import "LHIJKPlayer.h"
#import "Ijkplayer.h"
@interface LHIJKPlayer ()
{
id<IjkplayerProtocol> player;// IJKPlayer播放器自身的協議
}
@end
@implementation LHIJKPlayer
- (instancetype)init
{
self = [super init];
if (self) {
player = [[Ijkplayer alloc] init];// 初始化IJKPlayer播放器對象
}
return self;
}
// 播放
- (NSString *)lh_play{
return [player i_play];
}
// 暫停
- (NSString *)lh_pause{
return [player i_pause];
}
// 停止
- (NSString *)lh_stop{
return [player i_stop];
}
- (void)dealloc
{
player = nil;
}
@end
(2)策略模式中另一個核心類Context的定義
通用播放器類LHPlayer的定義。根據不同的策略選擇不同的算法。
#import <Foundation/Foundation.h>
#import "LHPlayerProtocol.h"
// 播放器的類型
typedef enum : NSUInteger {
EPlayerType_AVPlayer,
EPlayerType_IJKPlayer
} EPlayerType;
@interface LHPlayer : NSObject
- (instancetype)initWithType:(EPlayerType)type;
/**
* 開啟視頻
*
* @return 描述(只為舉例需要)
*/
- (NSString *)play;
/**
* 暫停視頻
*
* @return 描述(只為舉例需要)
*/
- (NSString *)pause;
/**
* 停止播放
*
* @return 描述(只為舉例需要)
*/
- (NSString *)stop;
@end
#import "LHPlayer.h"
#import "LHPlayerProtocol.h"
#import "LHAVPlayer.h"
#import "LHIJKPlayer.h"
@interface LHPlayer ()
{
id<LHPlayerProtocol> player;
}
@end
@implementation LHPlayer
- (instancetype)initWithType:(EPlayerType)type
{
self = [super init];
if (self) {
[self initPlayerWithType:type];
}
return self;
}
// 初始化播放器
- (void)initPlayerWithType:(EPlayerType)type{
switch (type) {
case EPlayerType_AVPlayer:
{
player = [[LHAVPlayer alloc] init];
break;
}
case EPlayerType_IJKPlayer:
{
player = [[LHIJKPlayer alloc] init];
break;
}
}
}
//開啟視頻
- (NSString *)play{
return [player lh_play];
}
//暫停視頻
- (NSString *)pause{
return [player lh_pause];
}
//停止播放
- (NSString *)stop{
return [player lh_stop];
}
@end
下面看客戶端的調用
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
@end
#import "ViewController.h"
#import "LHPlayer.h"
@interface ViewController ()
{
LHPlayer *player;
}
@property (weak, nonatomic) IBOutlet UIButton *btnAVPlayer;
@property (weak, nonatomic) IBOutlet UIButton *btnIjkplayer;
@property (weak, nonatomic) IBOutlet UILabel *lbState;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self initPlayerWithType:EPlayerType_IJKPlayer];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
// 初始化播放器
- (void)initPlayerWithType:(EPlayerType)type{
if (player) {
player = nil;
}
player = [[LHPlayer alloc] initWithType:type];
}
#pragma mark -
#pragma makr Button Event
// 選擇AVPlayer
- (IBAction)btnAVPlayerEvent:(UIButton *)sender {
sender.selected = YES;
_btnIjkplayer.selected = NO;
[self initPlayerWithType:EPlayerType_AVPlayer];
}
// 選擇Ijkplayer
- (IBAction)btnIjkplayerEvent:(UIButton *)sender {
sender.selected = YES;
_btnAVPlayer.selected = NO;
[self initPlayerWithType:EPlayerType_IJKPlayer];
}
// 播放器播放視頻
- (IBAction)btnPlayerEvent:(UIButton *)sender {
_lbState.text = player ? [player play] : @"播放器為空";
}
// 播放器暫停視頻
- (IBAction)btnPauseEvent:(UIButton *)sender {
_lbState.text = player ? [player pause] : @"播放器為空";
}
// 播放器停止視頻
- (IBAction)btnStopEvent:(UIButton *)sender {
_lbState.text = player ? [player stop] : @"播放器為空";
}
@end
大家可以看到,客戶端切換播放器只要替換一下枚舉值就可以了輕松切換了,而且哪個播放器火了,擴展新的播放器也是輕而易舉,不對客戶端造成任何影響。這就是策略模式的好處所在。
3.5 php中的應用
<?php
/**
*策略模式
*定義一系列的算法,把每一個算法封裝起來,并且使它們可相互替換。
*本模式使得算法可獨立于使用它的客戶變化
*/
/**
*出行旅游
*/
interface TravelStrategy{
public function travelAlgorithm();
}
/**
*具體策略類(ConcreteStrategy)
*1:乘坐飛機
*/
class AirPlanelStrategy implements TravelStrategy{
public function travelAlgorithm(){
echo"travelbyAirPlain","<BR>\r\n";
}
}
/**
*具體策略類(ConcreteStrategy)
*2:乘坐火車
*/
class TrainStrategy implements TravelStrategy{
public function travelAlgorithm(){
echo"travelbyTrain","<BR>\r\n";
}
}
/**
*具體策略類(ConcreteStrategy)
*3:騎自行車
*/
class BicycleStrategy implements TravelStrategy{
public function travelAlgorithm(){
echo"travelbyBicycle","<BR>\r\n";
}
}
/**
*
*環境類(Context):
*用一個ConcreteStrategy對象來配置。
*維護一個對Strategy對象的引用。可定義一個接口來讓Strategy訪問它的數據。
*算法解決類,以提供客戶選擇使用何種解決方案:
*/
class PersonContext{
private $_strategy = null;
public function __construct(TravelStrategy $travel){
$this->_strategy=$travel;
}
/**
*旅行
*/
public function setTravelStrategy(TravelStrategy $travel){
$this->_strategy=$travel;
}
/**
*旅行
*/
public function travel(){
return $this->_strategy->travelAlgorithm();
}
}
//乘坐火車旅行
$person=new PersonContext(new TrainStrategy());
$person->travel();
//改騎自行車
$person->setTravelStrategy(new BicycleStrategy());
$person->travel();
?>
3.6 java中的應用
假設現在要設計一個販賣各類書籍的電子商務網站的購物車系統。一個最簡單的情況就是把所有貨品的單價乘上數量,但是實際情況肯定比這要復雜。比如,本網站可能對所有的高級會員提供每本20%的促銷折扣;對中級會員提供每本10%的促銷折扣;對初級會員沒有折扣。
根據描述,折扣是根據以下的幾個算法中的一個進行的:
算法一:對初級會員沒有折扣。
算法二:對中級會員提供10%的促銷折扣。
算法三:對高級會員提供20%的促銷折扣。
使用策略模式來實現的結構圖如下:
抽象折扣類
public interface MemberStrategy {
/**
* 計算圖書的價格
* @param booksPrice 圖書的原價
* @return 計算出打折后的價格
*/
public double calcPrice(double booksPrice);
}
初級會員折扣類
public class PrimaryMemberStrategy implements MemberStrategy {
@Override
public double calcPrice(double booksPrice) {
System.out.println("對于初級會員的沒有折扣");
return booksPrice;
}
}
中級會員折扣類
public class IntermediateMemberStrategy implements MemberStrategy {
@Override
public double calcPrice(double booksPrice) {
System.out.println("對于中級會員的折扣為10%");
return booksPrice * 0.9;
}
}
高級會員折扣類
public class AdvancedMemberStrategy implements MemberStrategy {
@Override
public double calcPrice(double booksPrice) {
System.out.println("對于高級會員的折扣為20%");
return booksPrice * 0.8;
}
}
價格類
public class Price {
//持有一個具體的策略對象
private MemberStrategy strategy;
/**
* 構造函數,傳入一個具體的策略對象
* @param strategy 具體的策略對象
*/
public Price(MemberStrategy strategy){
this.strategy = strategy;
}
/**
* 計算圖書的價格
* @param booksPrice 圖書的原價
* @return 計算出打折后的價格
*/
public double quote(double booksPrice){
return this.strategy.calcPrice(booksPrice);
}
}
客戶端
public class Client {
public static void main(String[] args) {
//選擇并創建需要使用的策略對象
MemberStrategy strategy = new AdvancedMemberStrategy();
//創建環境
Price price = new Price(strategy);
//計算價格
double quote = price.quote(300);
System.out.println("圖書的最終價格為:" + quote);
}
}
3.7 C#實現
namespace StrategyPattern
{
// 所得稅計算策略
public interface ITaxStragety
{
double CalculateTax(double income);
}
// 個人所得稅
public class PersonalTaxStrategy : ITaxStragety
{
public double CalculateTax(double income)
{
return income * 0.12;
}
}
// 企業所得稅
public class EnterpriseTaxStrategy : ITaxStragety
{
public double CalculateTax(double income)
{
return (income - 3500) > 0 ? (income - 3500) * 0.045 : 0.0;
}
}
public class InterestOperation
{
private ITaxStragety m_strategy;
public InterestOperation(ITaxStragety strategy)
{
this.m_strategy = strategy;
}
public double GetTax(double income)
{
return m_strategy.CalculateTax(income);
}
}
class App
{
static void Main(string[] args)
{
// 個人所得稅方式
InterestOperation operation = new InterestOperation(new PersonalTaxStrategy());
Console.WriteLine("個人支付的稅為:{0}", operation.GetTax(5000.00));
// 企業所得稅
operation = new InterestOperation(new EnterpriseTaxStrategy());
Console.WriteLine("企業支付的稅為:{0}", operation.GetTax(50000.00));
Console.Read();
}
}
}
4. 特性總結
*策略模式的重心
策略模式的重心不是如何實現算法,而是如何組織、調用這些算法,從而讓程序結構更靈活,具有更好的維護性和擴展性。
- 算法的平等性
策略模式一個很大的特點就是各個策略算法的平等性。對于一系列具體的策略算法,大家的地位是完全一樣的,正因為這個平等性,才能實現算法之間可以相互替換。所有的策略算法在實現上也是相互獨立的,相互之間是沒有依賴的。
所以可以這樣描述這一系列策略算法:策略算法是相同行為的不同實現。
- 運行時策略的唯一性
運行期間,策略模式在每一個時刻只能使用一個具體的策略實現對象,雖然可以動態地在不同的策略實現中切換,但是同時只能使用一個。
- 公有的行為
經常見到的是,所有的具體策略類都有一些公有的行為。這時候,就應當把這些公有的行為放到共同的抽象策略角色Strategy類里面。當然這時候抽象策略角色必須要用Java抽象類實現,而不能使用接口。
這其實也是典型的將代碼向繼承等級結構的上方集中的標準做法。
5. 優點和缺點
優點:
(1)策略模式提供了管理相關的算法族的辦法。策略類的等級結構定義了一個算法或行為族。恰當使用繼承可以把公共的代碼轉移到父類里面,從而避免重復的代碼。
(2)策略模式提供了可以替換繼承關系的辦法。繼承可以處理多種算法或行為。如果不是用策略模式,那么使用算法或行為的環境類就可能會有一些子類,每一個子類提供一個不同的算法或行為。但是,這樣一來算法或行為的使用者就和算法或行為本身混在一起。決定使用哪一種算法或采取哪一種行為的邏輯就和算法或行為的邏輯混合在一起,從而不可能再獨立演化。繼承使得動態改變算法或行為變得不可能。
(3)使用策略模式可以避免使用多重條件轉移語句。多重轉移語句不易維護,它把采取哪一種算法或采取哪一種行為的邏輯與算法或行為的邏輯混合在一起,統統列在一個多重轉移語句里面,比使用繼承的辦法還要原始和落后。
缺點:
(1)客戶端必須知道所有的策略類,并自行決定使用哪一個策略類。這就意味著客戶端必須理解這些算法的區別,以便適時選擇恰當的算法類。換言之,策略模式只適用于客戶端知道所有的算法或行為的情況。
(2)策略模式造成很多的策略類,每個具體策略類都會產生一個新類。有時候可以通過把依賴于環境的狀態保存到客戶端里面,而將策略類設計成可共享的,這樣策略類實例可以被不同客戶端使用。換言之,可以使用享元模式來減少對象的數量。