Design Pattern之路——策略模式,接口的魔力

前言

该文主要通过一个场景的实现来介绍策略模式,当然对于设计模式本身的理解,每个人都有自己的理解,如果角度不同,看法自然不同,还请大家不吝指教,多多交流。

定义

引用《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方法即可。
老板看了这个设计之后立刻就开始破口大骂,因为这是一个非常糟糕的设计。原因如下:

  1. 如果我们有一百个角色,然后至少有一半的角色他的fight方法的逻辑是一样的,那就就产生了很多不必要的代码
  2. 不利于扩展。我们的fight逻辑都写死在每个子类中,如果需求改变了,那就要逐个子类修改,而现实是需求的改变是常态。
  3. 可能不是每个角色都需要fight这个动作
  4. 如果有新的方法加入到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接口的实现类,我认为这也是策略模式的一种。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值