理解策略模式
策略模式定义了算法族,不同的策略实现之间可以互相替换,让算法的变化独立于使用算法的客户。简要的UML示例图如下:
这里的算法可以理解为对传入参数的处理或者干脆就是一个成员函数/方法也行。例如:如下java代码,一个编辑器对LInux/Window平台的换行符实现不同的处理策略,Editor类与FormatStrategy策略接口相依赖。可以使自身能够有对字符串的不同处理,这一不同的处理行为并不依赖于子类继承重写某一方法。
public interface FormatStrategy { void dealEndLine(StringBuilder str); } public class LinuxFormatStrategy implements FormatStrategy { public void dealEndLine(StringBuilder str) { if (str.toString().endsWith("\r\n")) { str.replace(str.length()-2,str.length(), "\n"); } } } public class WindowFormatStrategy implements FormatStrategy { @Override public void dealEndLine(StringBuilder str) { if (str.toString().endsWith("\n") && !str.toString().endsWith("\r\n")) { str.replace(str.length()-1, str.length(), "\r\n"); } } } import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; public class Editor { private FormatStrategy format; private ArrayList<StringBuilder> content; ArrayList<StringBuilder> getContent(){ return content; } void setContent(ArrayList<StringBuilder> content) { this.content=content; } public FormatStrategy getFormatStrategy() { return format; } public void setFormatStrategy(FormatStrategy format) { this.format = format; } public void formatContent() { for(StringBuilder str:content) { format.dealEndLine(str); } } }
public interface CompareStrategy { boolean less(String str1, String str2); } public class SizeCmpStrategy implements CompareStrategy { @Override public boolean less(String str1, String str2) { return str1.length() < str2.length(); } } public class IgnoreCaseCmpStrategy implements CompareStrategy { @Override public boolean less(String str1, String str2) { return str1.compareToIgnoreCase(str2) < 0; } } public class Formatter { private CompareStrategy cmp; public CompareStrategy getCmp() { return cmp; } public void setCmp(CompareStrategy cmp) { this.cmp = cmp; } }
通常可以通过继承,重写父类的方法,使不同子类具有多态的行为。策略模式可以提供了替代继承的另一种方法。如果我们需要具备不同功能的类,我们可以不必总是通过继承某一父类实现不同的行为。而是通过注入不同的Strategy使其可以具备不同的行为。再举一个例子:
在一个游戏的角色随着升级的过程中,不同等级的英雄有不同的攻击和防守。可以将这变化的攻击和防守进行封装。如下面的Java代码:
public interface Attack { void attack(); } public interface Defend { void defend(); } public class PrimaryAttack implements Attack { @Override public void attack() { System.out.println("初级进攻"); } } public class PrimaryDefend implements Defend { @Override public void defend() { System.out.println("初级防守"); } } public class SeniorAttack implements Attack { @Override public void attack() { System.out.println("高级进攻"); } } public class SeniorDefend implements Defend { @Override public void defend() { System.out.println("高级防守"); } } public class Hero { private Attack attack; public Attack getAttack() { return attack; } public void setAttack(Attack attack) { this.attack = attack; } private Defend defend; public Defend getDefend() { return defend; } public void setDefend(Defend defend) { this.defend = defend; } public void doAttack() { attack.attack(); } public void doDefend() { defend.defend(); } public static void main(String[] args){ Hero hero=new Hero(); hero.setAttack(new PrimaryAttack()); hero.setDefend(new PrimaryDefend()); hero.doAttack(); hero.doDefend(); System.out.println("升级啦"); hero.setAttack(new SeniorAttack()); hero.setDefend(new SeniorDefend()); hero.doAttack(); hero.doDefend(); } }
//程序输出:
/*
初级进攻
初级防守
升级啦
高级进攻
高级防守
*/
由上述的例子,我们可以得出策略模式优点:这是一种可以替代继承的方法。并且这种行为是可以动态改变的并且不像继承一样硬编码。也可以说是组合优于继承的一个实践。
2. C++模板和策略模式
上述Java实现,C++可以很方便的进行类似的实现。这里再介绍一下基于C++模板的策略模式实现。C++的模板编程可以作为另外一种策略模式的实现,并且可能是更好的实现。将Strategy作为模板参数,开发出更为方便(隐式接口),高效率(编译期绑定)的程序。基于C++模板的范型编程,是使用对接口的约定进行编程,这种约定
是隐性的,也就是可以具有低侵入性的有点。所谓的低侵入性可以理解为对代码实现更少的要求,不用强制要求实现某一接口。通常我们在实现设计模式时,经常可以发现各个类的对应关系都比较间接,导致看起来比较复杂。C++模板策略模式的实现则不会有类似的情况,这是C++模板隐式接口的低侵入式所带来的好处。如:
#include <iostream> class PrimaryAttack{ public: void attack(){ std::cout<<"进行初级进攻"<<std::endl; } }; class PrimaryDefend{ public: void defend(){ std::cout<<"进行初级防守"<<std::endl; } }; class SeniorDefend{ public: void defend(){ std::cout<<"进行高级防守"<<std::endl; } }; class SeniorAttack{ public: void attack(){ std::cout<<"进行高级进攻"<<std::endl; } }; template<typename Attack, typename Defend> class Hero{ public: Hero():attack(),defend(){} void doAttack() { attack.attack(); } void doDefend(){ defend.defend(); } private: Attack attack; Defend defend; }; int main(){ std::cout<<"我是初级英雄"<<std::endl; Hero<PrimaryAttack, PrimaryDefend> primary_hero; primary_hero.doAttack(); primary_hero.doDefend(); std::cout<<"我是高级英雄"<<std::endl; Hero<SeniorAttack, SeniorDefend> senior_hero; senior_hero.doAttack(); senior_hero.doDefend(); } //程序输出: /* 我是初级英雄 进行初级进攻 进行初级防守 我是高级英雄 进行高级进攻 进行高级防守 */
Note:由于模板属于编译时多态,因此行为属于编译期绑定,不能像之前Java实现那样对策略进行动态的改变。
使用模板实现策略模式,带来了代码的简洁,不再需要定义接口,再进行实现。由于C++模板编程隐式接口约定,因此需要以文档的形式对策略的设计进行规定,如规定 Attack, Defend类型参数必须实现attack(),defend()方法。
与基于C++模板的策略模式相比,Java的实现比较繁琐,基于C++模板的实现更为轻量级。从编码的实现可以看出,类似于Java的策略模式的实现(C++也可以模仿实现)需要首先要有一个接口,类通过实现接口,而其它类面向这一接口进行依赖。而基于C++模板的策略模式的实现,则是一种低侵入式的实现,代码简洁。