软件设计模式(Design pattern),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。
本文主要介绍设计模式中的策略模式,将对其目的、结构进行分析,包括各个模块的内聚度和模块之间的耦合度,并提供C++范例,说明封装方法。
一、策略模式目的
定义一系列算法,把它们一个个封装起来,并且时它们可以相互替换,使得算法可以独立于使用它们的客户端而变化。
二、为什么要使用策略模式?
试想一个程序需要支持不同算法来分析一个文本流;如果程序直接包含分析文本算法的代码,那么当需要支持多种分析算法的时候程序将变得庞大且难以维护,算法和使用算法的代码高度耦合在一起,增加新的算法或改变现有算法将十分困难。
那么如果定义一个算法接口类,不同算法作为不同的子类继承这个接口呢?这也会导致一些问题,如动态改变算法会比较困难,最后你得到了一堆相关的类,它们之间唯一的差别是所使用的算法或者行为。
三、策略模式结构
这个时候策略模式便应运而生了。我们定义一个抽象的Strategy类包含一个Algorithm接口,使用子类ConcreteStrategyA/B/C来支持具体策略;Context类由一个Strategy对象配置,并维护对它的引用。整个结构如下:
通常将Strategy实现为无状态的对象,其余的状态都由Context来维护,Context在每一次对Strategy对象的请求中都将这个状态传递过去,Strategy对象不应该在各次调用之间维护状态。
具体实现上需要考虑如何让Context传递给Strategy所需的状态。
方法一,可以让Context将数据放在参数中传递给Strategy操作,这使得Strategy和Context解耦,但另一方面Context可能发送一些Strategy不需要的数据。
方法二,让Context自身作为一个参数传递给Strategy,该Strategy再显示地调用Context方法请求数据,或者Strategy可以存储它对Context的一个引用,这样根本不需要在调用时传递任何东西。但现在Context必须对它的数据定义一个更为精细的接口,这将Strategy和Context紧密耦合在一起。
方法三,将Strategy作为Context的模板参数。但这种方式仅仅当满足如下条件时才可以用:①可以在编译时选择Strategy;②不需要在运行时改变。
template<class Astrategy> class Context{ public: void operation(){ theStrategy.DoAlgorithm(); } private: Astrategy theStrategy; }; class MyStrategy{ public: void DoAlgorithm(); }; ContextaContext;
使用模板不需要给Strategy定义接口的抽象类。把Strategy作为一个模板参数也使得可以将一个Strategy和它的Context静态绑定在一起,从而提高效率。
四、示例分析
假设我们有一个APP,用户登录时支持多种方式验证用户身份,包含Basic、Digest、OpenID、OAuth,我们首先想到可以用如下方式验证代码
class BasicAuth {} class DigestAuth {} class OpenIDAuth {} class OAuth {} class AuthProgram { runProgram(authStrategy:any, ...) { this.authenticate(authStrategy) // ... } authenticate(authStrategy:any) { switch(authStrategy) { if(authStrategy == "basic") useBasic() if(authStrategy == "digest") useDigest() if(authStrategy == "openid") useOpenID() if(authStrategy == "oauth") useOAuth() } } }
这种方式需要让AuthProgram以来一长串switch-case,并且当我们只需要使用一种验证方式时,也得进行一长串判断。而如果我们使用Strategy Pattern,可以给所有验证方式定义公共接口:
interface AuthStrategy { auth(): void; } class Auth0 implements AuthStrategy { auth() { log('Authenticating using Auth0 Strategy') } } class Basic implements AuthStrategy { auth() { log('Authenticating using Basic Strategy') } } class OpenID implements AuthStrategy { auth() { log('Authenticating using OpenID Strategy') } }
而AuthenProgram可以简化许多:
class AuthProgram { private _strategy: AuthStrategy use(strategy: AuthStrategy) { this._strategy = strategy return this } authenticate() { if(this._strategy == null) { log("No Authentication Strategy set.") } this._strategy.auth() } route(path: string, strategy: AuthStrategy) { this._strategy = strategy this.authenticate() return this } }
调用Authen:
// Authenticating using OpenID Strategy new AuthProgram().use(new OpenID()).authenticate()
可以看到现在的AuthenProgram没有了一长串条件判断语句,我们只需要为其设置好Strategy,让AuthenProgram直接调用即可,实现AuthenProgram与具体的验证算法实现方式解耦。需要什么方式进行验证,只需要传入验证Strategy给验证程序即可。