前言
该文主要通过一个场景的实现来介绍策略模式,当然对于设计模式本身的理解,每个人都有自己的理解,如果角度不同,看法自然不同,还请大家不吝指教,多多交流。
定义
引用《First Head 设计模式》一书中对策略模式(strategy Pattern)的定义:
策略模式定义了算法簇,分别封装起来,让它们之间可以相互替换。让算法独立于使用者。
我认为策略模式的精华就在于它能够让算法独立于使用者,可以像零件一样自由替换使用。
与此同时,这里也符合我们的设计原则:多用组合,少用继承,封装变化。
场景
现在我们通过一个场景去慢慢了解策略模式的美妙之处,同时体验善用接口的好处。
场景:现在我们要为公司做一个角色游戏,采用OO编程思想实现。客户要求我们为游戏里面设计角色的实现
好,上面就是客户的要求(假设就这么简单的要求吧),我们现在来实现它。
方案一:
程序员阿茂立马开始实现
实现代码:
//角色抽象类
public abstract class Character {
private String characterName ;
public abstract void fight();
public String getCharacterName() {
return characterName;
}
public void setCharacterName(String characterName) {
this.characterName = characterName;
}
}
//角色实现类
public class SuperMan extends Character{
public SuperMan() {
this.setCharacterName("超人");
}
@Override
public void fight() {
System.out.println("我是"+this.getCharacterName());
System.out.println("发动猛拳攻击");
}
}
如上可以看到方案一的设计非常简单,就一个角色抽象类,以及一个超人子类,如果需要更多的角色就继承Character类,如果fight的逻辑不通去重写它的fight方法即可。
老板看了这个设计之后立刻就开始破口大骂,因为这是一个非常糟糕的设计。原因如下:
- 如果我们有一百个角色,然后至少有一半的角色他的fight方法的逻辑是一样的,那就就产生了很多不必要的代码
- 不利于扩展。我们的fight逻辑都写死在每个子类中,如果需求改变了,那就要逐个子类修改,而现实是需求的改变是常态。
- 可能不是每个角色都需要fight这个动作
- 如果有新的方法加入到character中,那每个子类都要实现,但是其中一些类又是不需要该方法的
好我们现在利用接口对它进行扩展
方案二
我们把fight方法抽象出来,让它成为接口,凡是需要fight动作的我们实现该接口。好那现在就变成如下的样子了:
实现代码:
//角色抽象类
public abstract class Character {
private String characterName ;
public String getCharacterName() {
return characterName;
}
public void setCharacterName(String characterName) {
this.characterName = characterName;
}
//.....其他方法
}
//Weapon接口
public interface Weapon {
public void fight() ;
}
//实现类
public class SuperMan extends Character implements Weapon{
public SuperMan() {
this.setCharacterName("超人");
}
@Override
public void fight() {
System.out.println("我是"+this.getCharacterName());
System.out.println("发动猛拳攻击");
}
}
如上,我们的逻辑已经发生了改变,如果我们的角色需要fight动作我们才继承Weapon接口(表示身上装有武器,可以发动攻击),然后调用fight方法发动攻击。如果我们没有继承武器接口的子类,我们可以抛出错误,或者只是喊出自己的角色名字,然后说自己不会打架。
如此一来就解决了上面的部分问题,比如一些角色不需要fight动作的,我们就不用继承Weapon接口了。扩展性也好了一些,如果有新的方法需要实现,比如dance方法,我们同样只需要定义Dance接口,让角色子类实现它就好了。
但是,这仍然是一个糟糕的解决方法,因为我们仍然会造成大量代码的重复,以及修改麻烦。
试想一下,如果一个我们一万个角色中有一半的weapon都是一样的,也就是fight的逻辑是一样的,那样重复代码非常多,并且如果我们在游戏中,一个角色换了武器,那么他的fight逻辑就要改变,这样的设计无法做到这样的要求,因为我们把逻辑写死在实现类中了。
我们还需要继续改进。
最终方案
现在我们回到我们所讨论的策略模式上,策略模式说到:把算法簇封装起来,让他们像部件一样可以替换使用。
如此看来,策略模式正适合用来解决我们当前的问题。
我们先想一下,什么总是改变的,我们把它封装起来,以期待达到复用的效果,并且可以达到可以替换的效果。
没错,就是角色的行为。
角色中有各种各样的行为,比如fight,dance。。。。我们把他们封装起来,变成Weapon、Dance接口,然后具体如何打架,如何跳舞,由这些接口的实现类去实现,这也就是设计原则中的一中:面对接口编程
现在我们开始重新设计:
** 实现代码: **
//抽象角色类,拥有一个Weapon接口的成员变量
public abstract class Character {
private Weapon weapon ;
private String characterName ;
public void fight(){
System.out.println("我是"+this.getCharacterName());
getWeapon().userWeapon();
}
public Weapon getWeapon() {
return weapon;
}
public void setWeapon(Weapon weapon) {
this.weapon = weapon;
}
public String getCharacterName() {
return characterName;
}
public void setCharacterName(String characterName) {
this.characterName = characterName;
}
}
//Weapon接口
public interface Weapon {
public void userWeapon() ;
}
//子类super
public class SuperMan extends Character {
public SuperMan(Weapon weapon) {
this.setWeapon(weapon);
this.setCharacterName("超人");
}
}
//子类葫芦娃
public class Huluwa extends Character {
public Huluwa(Weapon weapon) {
this.setWeapon(weapon);
this.setCharacterName("葫芦娃");
}
}
//子类蜡笔小新
public class LaBiXiaoXin extends Character {
public LaBiXiaoXin(Weapon weapon) {
this.setWeapon(weapon);
this.setCharacterName("蜡笔小新");
}
}
//Weapon实现类Punch
public class Punch implements Weapon {
@Override
public void userWeapon() {
System.out.println("发动猛拳攻击");
}
}
//Laser
public class Laser implements Weapon {
@Override
public void userWeapon() {
System.out.println("发射镭射激光");
}
}
//不会打架的实现类Nofight
public class NoFight implements Weapon {
@Override
public void userWeapon() {
System.out.println("不会打架");
}
}
以上就是我们利用策略模式实现的方案,现在我们无论那个角色都可以切换武器,进行不同的fight动作,不会有代码的重复,扩展性好,如果我们有其他行为,我们就定义新的新的接口,并且在我们的角色父类上持有该接口的变量即可,在写一个调用改接口的公用方法。
上面的方案同时也符合很多设计原则。如:
- 封装变化
- 多用组合,少用继承
- 面对接口编程
效果
说了那么多,我们来看看最终方案的效果
public class Main {
public static void main(String[] args) {
//创建一个超人角色,给他一把武器拳头
Character superman = new SuperMan(new Punch()) ;
//超人开始进行fight动作
superman.fight();
//这个时候超人打不过,超人想要使用镭射激光,切换fight逻辑
superman.setWeapon(new Laser());
//开打
superman.fight();
//此时多了一个人角色葫芦娃,为了搞怪,让他也可以使用镭射激光
Character huluwa = new Huluwa(new Laser());
huluwa.fight();
//现在有了一个蜡笔小新的角色,小新可是一个可爱的男生,不会打架的
Character labixiaoxin = new LaBiXiaoXin(new NoFight()) ;
labixiaoxin.fight();
}
}
现在来看运行结果
看到了运用设计模式的魅力和善用接口的魔力了吗?
总结
策略模式并不是写死的,大家可以依照自己的理解去实现。只要符合他的精髓(封装算法,让算法独立于使用者)即可。另外,经常使用Spring的同学在这里应该有感到策略模式似乎在哪里见过,是的策略模式我们其实在写spring的时候都无意间用过,比如我们的Controller类中经常自动注入不同的Service,而注入的Service是Service接口的实现类,我认为这也是策略模式的一种。