策略模式(Strategy):定义了算法家族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化不会影响到使用算法的用户。
策略模式是一种定义一系列算法的方法,从概念上看,所有这些算法完成的都是相同的工作,只是实现上不同,它可以以相同的方式调用所有的算法,减少各种算法类与算法使用类之间的耦合。
策略模式是用来封装算法的,但在实践中发现,可以用它来封装几乎任何类型的规则,只要在分析过程中需要在不同时间应用不同的业务规则,就可以考虑使用策略模式来处理这种变化的可能性。
策略模式的好处:一是利用继承能有助于析取出不同算法中的公共功能;二是简化了单元测试,因为每个算法都有自己的实现类,可以通过自己的接口单独进行测试;三是将不同的行为封装到一个个独立的Strategy类中,可以在使用这些行为的类中消除条件语句,因为当不同的行为堆叠在一个类中时,就难以避免用条件语句来选择合适的行为。
策略模式的UML类图如下所示:
策略模式的实现主要分三个部分:
1. 抽象策略对象:析取算法/规则家族的公共部分,并提供调用算法实现方法的接口,即图中的AlgorithmInterface抽象方法;
2. 具体策略对象:由抽象策略对象派生,主要是实现算法的具体逻辑,即实现继承自父类的AlgorithmInterface方法;
3. 环境对象:引用抽象策略对象,并供客户端调用。通过其中的某个方法(ContextInterface)来实际调用具体策略对象的AlgorithmInterface方法。
下面的代码基于策略模式和反射模拟了一个两个只会火球术和治疗术魔法师随机进行对战的程序:
魔法师实体类:
public class Role { private double hp; private double mp; private String name; public static final int TYPE_HP = 0; public static final int TYPE_MP = 1; public Role(String name, double hp, double mp) { this.name = name; this.hp = hp; this.mp = mp; } public void change(int type, double value) { switch (type) { case TYPE_HP: this.hp += value; break; case TYPE_MP: this.mp += value; break; } System.out.println(name + " " + (value >= 0 ? "+" : "") + value + (type == TYPE_HP ? "HP" : "MP")); } public double get(int type) { double value = 0; switch (type) { case TYPE_HP: value = this.hp; break; case TYPE_MP: value = this.mp; break; } return value; } public void show() { System.out.println(name + "(HP: " + this.hp + "; MP: " + this.mp + ")"); } public String getName() { return name; } }
抽象策略:
interface Skill { public void cast(Role from, Role to); }具体策略:
火球术实现类:
public class Fireball implements Skill { @Override public void cast(Role from, Role to) { if (from.get(Role.TYPE_MP) >= 5) { System.out.println(from.getName() + " casts Fireball to " +to.getName()); double damage = 10 + (int)(Math.random() * 1000 % 11); from.change(Role.TYPE_MP, -5); to.change(Role.TYPE_HP, -damage); } else { System.out.println(from.getName() + " has a rest"); from.change(Role.TYPE_MP, 5); } } }治疗术实现类:
public class Heal implements Skill { @Override public void cast(Role from, Role to) { if (from.get(Role.TYPE_MP) >= 10) { System.out.println(from.getName() + " cast Heal to himself"); from.change(Role.TYPE_HP, 20); from.change(Role.TYPE_MP, -10); } else { System.out.println(from.getName() + " has a rest"); from.change(Role.TYPE_MP, 5); } } }普通攻击实现类:
public class Hit implements Skill { @Override public void cast(Role from, Role to) { System.out.println(from.getName() + " hits " +to.getName()); to.change(Role.TYPE_HP, -5); } }环境对象,通过传入的参数利用Java的反射功能实例化匹配的策略类:
public class SkillContext { private Skill skill; private static final String[] SKILL_CLASS_NAME = { "com.strategy.skill.Hit", "com.strategy.skill.Fireball", "com.strategy.skill.Heal" }; public void setSkill(int type) { try { this.skill = (Skill) Class.forName(SKILL_CLASS_NAME[type]).newInstance(); // 反射 } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public void castSkill(Role from, Role to) { skill.cast(from, to); } public int size() { return SKILL_CLASS_NAME.length; } }客户端:
public class Main { public static void main(String[] args) { Role a = new Role("Adan", 100, 100); Role b = new Role("Brant", 100, 100); SkillContext context = new SkillContext(); int count = 1; while (a.get(Role.TYPE_HP) >= 0 && b.get(Role.TYPE_HP) >= 0) { System.out.println("#" + count++); context.setSkill((int)(Math.random() * 100) % context.size()); context.castSkill(a, b); context.setSkill((int)(Math.random() * 100) % context.size()); context.castSkill(b, a); a.show(); b.show(); } } }
当有更多技能的时候,继续实现Skill接口,并将SkillContext对象中的常量字符串数组放到外部文件中,就可以顺利遵循开放-封闭原则,并且拓展程序的功能。
从上面的例子可以很明显的看出,策略模式对于拓展新的逻辑规则是很方便的。但是,随着新策略类的扩展,类会越来越多。这虽然符合单一职责原则,但是越来越多的类也不太容易维护。