记一场精彩的篮球比赛——浅谈策略模式
策略模式
声明:本文为原创,如有转载请注明转载与原作者并提供原文链接,仅为学习交流,本人才识短浅,如有错误,敬请指正
虽然我本人比较讨厌一些很官方的术语定义,因为我经常弄不明白有些定义讲了个啥,但是为了让这篇博文显得不那么轻浮,所以我也就不能免俗的先将设计模式之策略模式的定义首先丢到各位看官面前。
策略模式定义了算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
第一眼这个定义看上去,难免会让人心生胆怯,又是算法族,又是封装,又是替换,似乎很复杂的样子,但是实际上,很好理解,不信,我们来一起看一场非常激烈的篮球比赛吧,相信看完这场比赛之后你就能大致掌握这个设计模式了。
PS:有人肯定要问了,为啥没有个UML图呢,其他人说设计模式都会咣当扔个UML图上来,你咋没有呢,是不是偷工减料,其实啊,真不是,在于我这个人呢,比较喜欢直接撸代码看代码,也没有说看UML图理解一个设计模式的,并不说UML图不重要,只是策略模式的UML图在网上一搜一大把,我还是不要当搬运工,复制粘贴了吧。
欢迎大家来到NBA赛场,今天的比赛呢,双方分别是湖人和骑士,科比与詹姆斯的宿命对决,作为篮球运动员,在场上,最重要的两件事是啥呢,没错,就是“投篮”和“传球”,投篮是为了得分,传球是为了更轻松的投篮得分,投篮有很多种方式,后仰投篮,三分远投,急停跳投,传球包括击地传球,不看人传球等,而在Java的设计模式中,我们可以把这些统统定义成“方法”。
我们首先抽象出来一个投篮相关的接口,它包含了一个方法:shoot(),即投篮
public interface ShootStrategy { public void shoot(); }
同样的,我们抽象出来一个传球相关的接口,它包含了一个方法:pass(),即传球
public interface PassStrategy { public void pass(); }
那么这两个接口有什么用呢,回到策略模式的定义,注意“算法族”这三个字,什么是算法族,如果后仰投篮是一个算法,三分远投是一个算法,急停跳投也是一个算法,我们就会发现他们的共同点是,他们都是投篮的算法,也就是说他们都是投篮的算法族,同理,击地传球与不看人传球也是传球的算法族,而后策略模式的定义说要分别封装他们,提到封装大家想到了什么呢,类!
接下来我们尝试封装一下后仰投篮算法和三分远投算法
public class BackwardShoot implements ShootStrategy { @Override public void shoot() { System.out.println("标志性的后仰跳投"); } }
封装后仰跳投算法,代码很简单,因为我们不希望算法的逻辑干扰到我们着眼于真正的重点——策略模式。
public class ThreeShoot implements ShootStrategy { @Override public void shoot() { System.out.println("神准的三分"); } }
封装三分远投算法。
同样的,我们也封装一下传球的算法,毕竟篮球离不开传球(科比:传球,传球是什么意思,误~)
public class DropPass implements PassStrategy { @Override public void pass() { System.out.println("诡异的击地传球"); } }
封装击地传球算法
public class NoLookPass implements PassStrategy { @Override public void pass() { System.out.println("一记精彩的不看人传球"); } }
封装不看人传球算法
好了,现在我们所有的策略都准备好了(原谅我这里直接使用了策略,不用惊吓,我们刚刚已经完成了策略模式最重要的部分),就差我们的运动员上场了。
我们首先定义一个篮球运动员的类。
public class BasketballPlayer { private String name; private ShootStrategy shootStrategy; private PassStrategy passStrategy; public void shoot(){ System.out.println("------" + name + "------"); shootStrategy.shoot(); } public void pass(){ System.out.println("------" + name + "------"); passStrategy.pass(); } public void setShootStrategy(ShootStrategy shootStrategy) { this.shootStrategy = shootStrategy; } public void setPassStrategy(PassStrategy passStrategy) { this.passStrategy = passStrategy; } public void selfIntroduction(){ System.out.println("我是个篮球运动员"); } public void setName(String name) { this.name = name; } }
看一下这段代码,我定义了三个变量,name,表示运动员的名字,然后是我自己定义的投篮接口以及传球接口,这俩干干巴巴,麻麻赖赖的接口放这里有啥用呢,盘他,我们往下看,我们会发现,篮球运动员的投篮,使用的是我们投篮接口里的shoot()方法实现,而传球呢,使用的是我们传球接口里的pass()方法实现,有人就会想了,我这俩接口都没方法体,调用个毛啊,稍安勿躁,继续往下看,下面是两个平平无奇的setter方法,然而,奥秘就是在这里,这个奥秘,我们一般叫他“解耦”,通过使用Java的接口回调,我们可以将任意对象传入这个类中供这个类使用,只要这个类实现了对应的接口即可,在这里就是投篮接口与传球接口,然后我们就会发现,刚才我们实现的后仰投篮,三分远投等,这里统统受用!
说了之久,我们的大明星怎么还没露面呢,继续
科比是一名(is-a)篮球运动员,所以
public class Kobe extends BasketballPlayer { @Override public void selfIntroduction(){ System.out.println("我的名字是科比"); } }
詹姆斯是一名(is-a)篮球运动员,所以
public class James extends BasketballPlayer { @Override public void selfIntroduction(){ System.out.println("我叫詹姆斯"); } }
注意,虽然两个类代码比较少,但是不要忘了,由于他俩继承了篮球运动员类,所以篮球运动员里的那些方法,他们也都拥有。
好啦,现在万事俱备,让我们开始这一场激动人心的比赛吧
public class LakerVsCavalier { public static void main(String[] args) { Kobe kobe = new Kobe(); kobe.selfIntroduction(); kobe.setName("科比"); James james = new James(); james.selfIntroduction(); james.setName("詹姆斯"); kobe.setPassStrategy(new NoLookPass()); kobe.setShootStrategy(new BackwardShoot()); kobe.pass(); kobe.shoot(); james.setPassStrategy(new DropPass()); james.setShootStrategy(new ThreeShoot()); james.pass(); james.shoot(); kobe.setPassStrategy(new DropPass()); kobe.setShootStrategy(new ThreeShoot()); kobe.pass(); kobe.shoot(); james.setPassStrategy(new NoLookPass()); james.setShootStrategy(new BackwardShoot()); james.pass(); james.shoot(); } }
虽然代码看起来很简单,只是让科比与詹姆斯两个对象shoot()与pass()而已,然而事情其实并没有那么简单,回顾策略模式的定义,“让它们之间可以互相替换”,这就是问题的关键,我们为同一个对象设置了不同的算法,科比可以先设置传球策略为不看人传球,设置投篮策略为后仰跳投,然后此封装的算法就会被科比对象中的pass()和shoot()方法调用,原因上文已述,然后,我们可以重新为科比对象设置不同的算法,我们把传球策略为击地传球,投篮策略设置为三分远投,然后科比再次传球(调用pass方法),就变成了击地传球,再次投篮(调用shoot方法),就变成了三分远投,詹姆斯对象同理。
我们来看一下执行结果
一切皆如我们所料。
至此,我们也就能理解策略模式定义的最后一句:此模式让算法的变化独立于使用算法的客户。
更重要的一点是,通过策略模式,我们真正的将易于变化的部分抽出来,使代码对修改关闭,对扩展开放,假设我们要添加一个新的策略——急停跳投,我们只需要通过实现投篮接口的方式新建一个急停跳投策略,然后把他set进科比,詹姆斯这种使用算法的对象即可,完全不用修改已有的调用算法的代码!
有兴趣的可以自己去实现看看哦
今天的篮球比赛转播到此结束,我是设计模式评论员JR,我们下次再见。