设计模式 - 策略模式

来源:互联网 发布:头发种植 知乎 编辑:程序博客网 时间:2024/06/05 04:00

Strategy Pattern,个人用的最多的一种模式之一。这种模式比较简单,但是却很有效。

意图

定义一系列的算法,把它们一个一个封装起来,并且使它们可以互相替换。本模式使得算法可独立于使用它的客户而变化。

结构

从结构上看策略模式还是蛮简单的,两个参与者:context和strategy。context会将它的客户的请求转发给context的策略对象。从而相应的策略对象可以完成客户的请求。

以前写过一个小小的网络监听软件,用来知道其他应用程序发送了什么文件。我们知道发送文件可以有很多方法:

1. 首先常用的ftp协议;

2. http协议也可以;

3. 还有其他的协议包括一些自定义协议。

网络抓包有很多办法,比如写一个SPI或者TDI驱动。都可以得到其他应用程序发送的数据。但是我们得到的是网络数据,也就是说这些数据会根据使用的协议不同而不同。比如发送一个文件,文件内容是hello world,那么假如使用http协议发送,网络抓包抓下来的数据就会像是:

xxxxx

hello world

xxxxx

文件内容处于数据包中,但是同时这个数据包还有其他的一些数据,比如http头。那么怎么提取出文件内容呢?这个时候就需要一套算法来提取。而且针对不同的协议需要不同的算法。我们可以用策略模式来封装不同协议的算法。

我们以ftp协议和http协议为例。

首先定义strategy基类:

class CProtocol{public:virtual string GetFileContent(const string& data) = 0;};

很简单,就提供了一个函数GetFileContent,也就是从输入的网络包数据里面提取文件内容。

看看具体的实现:

class CHTTP: public CProtocol{public://分析传进来的数据data,从中找出发送的文件内容virtual string GetFileContent(const string& data){cout << "parse HTTP protocol\n";return "xxxxxx";}};class CFTP: public CProtocol{public://分析传进来的数据data,从中找出发送的文件内容virtual string GetFileContent(const string& data){cout << "parse FTP protocol\n";return "yyyyy";}};

FTP和HTTP对应的类可以各自实现各自提取文件内容的算法。

看看context类:

class CProtocolContext{public:CProtocolContext(CProtocol* p, const string& path): m_protocol(p), m_cachepath(path){}//我们的例子是个网络监听软件,当有其他进程发送文件的时候,//这个函数将被调用。这里只是一个模拟void DataReceived(const string& data){string content = m_protocol->GetFileContent(data);cout << "save content: \"" << content << "\" to cache file: " << m_cachepath << "\n";}protected:string m_cachepath;CProtocol* m_protocol;};

context类里面有一个策略对象,同时我还放了一个cache文件的路径,意思是可以把得到的文件内容存到本地cache文件中。里面有个函数DataReceived(),当监听代码(比如SPI,TDI等)截获一个网络数据包的时候,这个函数会被调用。这样,Context就可以使用相应的策略来分析数据包,从而得到数据包中的文件内容。如:

void Pattern_Strategy(){CFTP* ftp = new CFTP();CProtocolContext* context = new CProtocolContext(ftp, "c:\\cache.txt");//假设监听代码收到了一些数据context->DataReceived("abcdaaaaaa");CHTTP* http = new CHTTP();context = new CProtocolContext(http, "c:\\cache.txt");//假设监听代码收到了一些数据context->DataReceived("abcdaaaaaa");}

我们可以看到,要切换策略,相当的简单,只需要创建一个新的策略,并传给context类就可以了。而且算法可以互相替换,因为它们的接口一样。当然使用不正确的策略,会导致得不到正确的结果。比如,我们得到了一些数据,这些数据是从ftp软件过来的,那么就可以用ftp算法来分析。如果发现一种软件不使用http和ftp,而是使用了bt协议,那么可以相应的增加一个strategy子类来处理bt协议。只需要创建一个bt协议的对象,并且传给context就可以了,不会影响到http和ftp。再如果,原来使用的是http1.0协议,现在想升级到http1.1,那么更新http协议strategy类就可以了,不会影响其他的strategy和context。
策略模式的优点:策略可以独立于它的客户而改变,比较容易切换和扩展。如上例中可以增加一个新的协议算法,并且可以很容易地切换不同的算法。

当然策略模式也有缺点:

1. 客户需要知道每个策略,这样才能选择一个正确的策略。往往我们在选择策略的时候还是使用了一个if-else或者switch。

2. 策略类和context类之间的通信,也是一种开销。

其实,策略模式还有个问题,就是谁来创建策略对象和销毁它?这个估计没有固定的套路吧,会有一些选择,比如:

1. 需要的时候创建,用完就销毁。

2. 将策略对象搞成singleton。其实状态模式里面经常可以这么干,但是策略模式的策略对象里面可能会保存一些上下文信息,因为策略对象通常封装了一些算法,可能会涉及一些上下文信息。那么假如搞成singleton的话,就不太合适了,因为策略对象内部的上下文数据可能是会变的。特别是并发的时候,估计会出问题。

3. 使用享元模式,将策略模式放到享元池里面。这样,需要使用策略模式的时候,可以先看看享元池里面有没有空闲的策略对象,如果有,就直接使用,如果没有,则新建一个。

可能还有其他办法,这里只是介绍几种常用的方法。

 

策略模式和状态模式的区别

如果仔细看策略模式的结构,我们会发现,这个图是不是跟状态模式一模一样啊?是的,确实一模一样。我把这个两种模式的结构图放到了一起,如下图。


哈哈,果然是一模一样。从结构上看,这两种模式是一模一样的,但是它们的意图完全不同:

1. 状态模式强调的是:内部状态的变化可以引起context的行为发生变化。比如例子中的主角可以根据状态的不同,可以有不同的碰撞结果。

2. 策略模式强调的是:Caller可以轻松地使用不同的算法。算法可以独立于客户而改变。

 

 

原创粉丝点击