《二 》策略模式
//战士抽象类
public abstract class Warrior {
public abstract void changeStance();
}
//狂暴战
public class FuryWarrior extends Warrior{
@Override
public void changeStance() {
System.out.println("切换至狂暴姿态");
}
}
//防战
public class TankWarrior extends Warrior {
@Override
public void changeStance() {
System.out.println("切换到防御姿态");
}
}
//武器战
public class WeaponWarrior extends Warrior {
@Override
public void changeStance() {
System.out.println("切换至战斗姿态");
}
}
//测试类
public class WarriorTest {
@Test
public void testWarrior(){
String type = "战场";
Warrior warrior;
switch (type) {
case "MT":
warrior = new TankWarrior();
warrior.changeStance();
break;
case "战场":
warrior = new WeaponWarrior();
warrior.changeStance();
break;
case "输出":
warrior = new FuryWarrior();
warrior.changeStance();
break;
default:
break;
}
}
}
通过上面这种硬编码的方式,在客户端进行逻辑判断(使用switch或者if-else)可以实现战士在不同场景下多的姿态切换,但是假设现在暴雪给战士又添加了一个新的姿态或者新的场景需要判断,那么就必须要在客户端代码中修改源码,非常不便于扩展,而且复杂的switch或者if-else语句都是非常不便于维护的,这时候策略模式就闪亮登场了!
策略模式:定义了算法家族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化不会影响到使用算法的客户端。
那么现在我们使用策略模式再把上面的场景实现一次:
//战士类(环境角色)
public class Warrior {
private Stance stance;
public Warrior(Stance stance) {
this.stance = stance;
}
public void changeStance(){
stance.getStance();
}
}
//姿态接口(抽象策略角色)
public interface Stance {
void getStance();
}
//防御姿态
public class TankStance implements Stance {
@Override
public void getStance() {
System.out.println("切换到防御状态");
}
}
//狂暴姿态
public class FuryStance implements Stance {
@Override
public void getStance() {
System.out.println("切换至狂暴状态");
}
}
//武器姿态
public class WeaponStance implements Stance {
@Override
public void getStance() {
System.out.println("切换至战斗状态");
}
}
//测试类
public class WarriorTest {
@Test
public void testWarrior(){
Warrior w1 = new Warrior(new TankStance());
w1.changeStance();
Warrior w2 = new Warrior(new FuryStance());
w2.changeStance();
Warrior w3 = new Warrior(new WeaponStance());
w3.changeStance();
}
}
通过使用策略模式实现战士切换姿态的场景需求时,如果需要添加新的姿态或者场景,那么只需要添加一个算法就行了,相比硬编码的方式更容易维护和扩展。策略模式的重心不是在算法,而是封装算法,通过组织和调用算法让程序结构更加灵活。
策略模式的优点:
(1) 策略模式定义了一系列的算法,所有这些算法都是完成相同的工作,只是实现不同,可以互相替换,减少了客户端和算法类之间的耦合
(2) 策略模式中的算法都可以进行单独的单元测试
(3) 策略模式避免了复杂的逻辑判断语句(switch或if-else)
策略模式的缺点:
(1) 通过上面的示例返现,客户端必须知道所有的策略类,并且知道应该使用哪一个策略类。
(2) 如果算法种类过多,那么策略类的数量就会过于庞大。
扩展:
使用简单工厂模式配合策略模式,可以让客户端不用自己来决定该使用哪一个策略,代码如下:
//生产状态对象的工厂类
public class StanceFactory {
public static Stance getStance(String type){
switch (type) {
case "MT":
return new TankStance();
case "输出":
return new FuryStance();
case "战场":
return new WeaponStance();
default:
return null;
}
}
}
//测试类
public class WarriorTest {
@Test
public void testWarrior(){
String type = "战场";
Stance stance = StanceFactory.getStance(type);
if (stance!=null){
Warrior warrior = new Warrior(stance);
warrior.changeStance();
}
}
}
通过配合使用简单工厂模式,我们可以让客户端只需传入场景就可以由工厂来选择对应的策略,虽然解决了客户端判断策略的压力,但是工厂类的switch语句又导致程序的维护性和扩展行较差。我们可以使用反射和配置文件来对工厂类进行改进从而解决这个问题。代码如下:
//生产状态对象的工厂类
public class StanceFactory {
private static Map<String, String> data;
static{
//这里使用Map模拟配置文件
data = new HashMap<>();
data.put("战场", "cn.eragon.domain.WeaponStance");
data.put("MT", "cn.eragon.domain.TankStance");
data.put("输出", "cn.eragon.domain.FuryStance");
}
public static Stance getStance(String type){
String className = data.get(type);
if (className!=null){
try {
return (Stance) Class.forName(className).newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return null;
}
}