三十二、策略模式

1. 简介

策略(Strategy)模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。策略模式是对算法的包装,是把使用算法的责任和算法本身分割开来,委派给不同
的对象管理。看到策略模式的时候有的时候跟简单工厂相比较,其实有很大的迷惑性,都是继承多态感觉没有太大的差异性,简单工厂模式是对对象的管理,策略模式是对行为的封装。可以先简单的看一下结构图:

三十二、策略模式_第1张图片
策略模式演示图

之前简单工厂是通过银行卡作为例子的简单工厂将不同的银行卡抽象出来,如果在策略模式中我们可以将每张银行卡的购物,吃饭,住房。。作为一个简单的消费策略抽象出来,也可以以操作系统类比,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  
#include  
#include  
using namespace std; 
  
//strategy抽象类,用作接口 
class Strategy 
{ 
public: 
  virtual string substitute(string str)=0; 
  virtual ~Strategy() 
  { 
    cout<<" in the destructor of Strategy"< strategy; 
 
     //在客户代码中加入算法(stategy)类型的指针。 
public: 
  ~Translator() 
  { 
    cout<<" in the destructor of Translator"< 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"<translate(str); 
  cout<<"---------------"< s1(new ChineseStrategy); 
  translator->set_strategy(s1); 
  cout<<"Chinese Strategy"<translate(str)< s2(new EnglishStrategy); 
  translator->set_strategy(s2); 
  cout<<"English Strategy"<translate(str)<

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 

/**
 *  Player开启视频
 *
 *  @return 描述(只为举例需要)
 */
- (NSString *)lh_play;

/**
 *  Player暂停视频
 *
 *  @return 描述(只为举例需要)
 */
- (NSString *)lh_pause; 

/**
 *  Player停止播放
 *
 *  @return 描述(只为举例需要)
 */
- (NSString *)lh_stop;

AVPlayer的算法封装

#import 
#import "LHPlayerProtocol.h"

@interface LHAVPlayer : NSObject

@end

#import "LHAVPlayer.h"
#import "AVPlayer.h"

@interface LHAVPlayer ()
{
    id 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 
#import "LHPlayerProtocol.h"

@interface LHIJKPlayer : NSObject

@end

#import "LHIJKPlayer.h"
#import "Ijkplayer.h"

@interface LHIJKPlayer ()
{
    id 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 
#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  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 
@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中的应用

\r\n";
    }
}
 
/**
*具体策略类(ConcreteStrategy)
*2:乘坐火车
*/
class TrainStrategy implements TravelStrategy{
    public function travelAlgorithm(){
        echo"travelbyTrain","
\r\n"; } } /** *具体策略类(ConcreteStrategy) *3:骑自行车 */ class BicycleStrategy implements TravelStrategy{ public function travelAlgorithm(){ echo"travelbyBicycle","
\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%的促销折扣。

使用策略模式来实现的结构图如下:

三十二、策略模式_第2张图片
计算折扣逻辑图

抽象折扣类

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抽象类实现,而不能使用接口。

这其实也是典型的将代码向继承等级结构的上方集中的标准做法。

三十二、策略模式_第3张图片
代码集中示意图

5. 优点和缺点

优点:
(1)策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族。恰当使用继承可以把公共的代码转移到父类里面,从而避免重复的代码。
(2)策略模式提供了可以替换继承关系的办法。继承可以处理多种算法或行为。如果不是用策略模式,那么使用算法或行为的环境类就可能会有一些子类,每一个子类提供一个不同的算法或行为。但是,这样一来算法或行为的使用者就和算法或行为本身混在一起。决定使用哪一种算法或采取哪一种行为的逻辑就和算法或行为的逻辑混合在一起,从而不可能再独立演化。继承使得动态改变算法或行为变得不可能。
(3)使用策略模式可以避免使用多重条件转移语句。多重转移语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重转移语句里面,比使用继承的办法还要原始和落后。
缺点:
(1)客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道所有的算法或行为的情况。
(2)策略模式造成很多的策略类,每个具体策略类都会产生一个新类。有时候可以通过把依赖于环境的状态保存到客户端里面,而将策略类设计成可共享的,这样策略类实例可以被不同客户端使用。换言之,可以使用享元模式来减少对象的数量。

你可能感兴趣的:(三十二、策略模式)