继承很不错
小帅刚毕业就进了一家游戏公司工作,公司正在开发一款新的即时战略游戏,领导让他设计游戏里的兵种及行为。
在第一个版本里,只有步兵,骑士和弓箭手三个兵种,每个兵种都有移动,停止,攻击和自愈四种行为。
小帅马上做了个简单的设计,每个兵种都能移动和停止,不同的兵种有不同的战斗方式和回血速度。
于是设计了Unit(兵种)抽象类,实现了move()和stop()方法,各个兵种继承该超类,然后根据自己的战斗特点实现超类的中的fight()和selfHealing()抽象方法。
子类重用的了父类的代码,又根据自己的特点实现了抽象方法,嗯,小帅觉得很满意,就在小组中做了分享。
“我觉得还应该加个投石车兵种,攻城利器”,小组成员提议。
“没问题,加个投石车的子类就行了”,小帅自信地说。
”可是投石车明明不能自愈,却还要实现父类的selfHealing()方法,不觉得很奇怪吗?”项目组的老王立马指出了问题。
接口怎么样?
小帅立马想到了接口,把自愈方法单独抽离出来,放到一个自愈接口里,有自愈能力的兵种都继承该接口。没自愈能力的投石车,就不用继承了。
”这样看上去好多了,但是代码不能复用,每个子类都要重新实现fight()和selfHealing()方法,以后我们要设计几十个兵种,每个兵种都得重新实现一遍,这样太麻烦了。”老王犀利得指出了问题所在。
小帅一时语塞。
针对接口编程,而不是针对实现编程
老王看了看小帅一脸迷茫的样子,托了托眼镜,继续说道:“我们要找出应用中经常变化的部分,把它们独立出来,和那些不需要变动的代码区分开来,这是个重要的设计原则。”
“在这个例子中,fight()和selfHealing()方法每个子类的实现都不一样,属于经常变化的部分,我们可以设计SelfHealingBeHavior和FightBeHavior接口,把战斗行为和自愈行为提取出来,这样就应用了一个设计原则:针对接口编程,而不是针对实现编程。”
“这里我们可以使用策略(Strategy)模式进行设计”,老王已经停不下来了,继续说道:
策略(Strategy)模式的定义如下:该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。
这里的战斗行为和自愈行为就是“算法”,我们把算法封装起来,各种战斗和自愈的实现方式都在子类中实现,客户端只要调用接口就行了,不用管具体是怎么实现的。
应用了策略模式后的类图:
说了这么多,也该上代码了。
FightBeHavior接口以及实现类
public interface FightBeHavior {
void fight();
}
public class FightWithSword implements FightBeHavior{
public void fight() {
System.out.println("攻击:看我的剑法");
}
}
public class FightWithArrow implements FightBeHavior{
public void fight() {
System.out.println("攻击:我射箭-->");
}
}
public class FightWithSpear implements FightBeHavior{
public void fight() {
System.out.println("攻击:看我的长枪");
}
}
public class FightWithStone implements FightBeHavior{
public void fight() {
System.out.println("攻击:我扔石头");
}
}
SelfHealingBeHavior接口以及实现类
public interface SelfHealingBeHavior {
void selfHealing();
}
public class SelfHealingFast implements SelfHealingBeHavior{
public void selfHealing() {
System.out.println("我每秒回血3%\n");
}
}
public class SelfHealingNormal implements SelfHealingBeHavior{
public void selfHealing() {
System.out.println("我每秒回血2%\n");
}
}
public class SelfHealingSlow implements SelfHealingBeHavior{
public void selfHealing() {
System.out.println("我每秒回血1%\n");
}
}
兵种类
public class Unit {
protected String name;
protected FightBeHavior fightBeHavior;
protected SelfHealingBeHavior selfHealingBeHavior;
public void move() {
System.out.println("我是:" + name);
System.out.println("向前挺进...");
}
public void stop() {
System.out.println("原地待命...");
}
public void setFightBeHavior(FightBeHavior fi) {
fightBeHavior = fi;
}
public void setSelfHealingBeHavior(SelfHealingBeHavior se) {
selfHealingBeHavior = se;
}
public void fight() {
fightBeHavior.fight();
}
public void selfHealing() {
selfHealingBeHavior.selfHealing();
}
}
public class Infantry extends Unit{
public Infantry() {
name = "步兵";
// 用剑攻击
fightBeHavior = new FightWithSword();
// 回血速度慢
selfHealingBeHavior = new SelfHealingSlow();
}
}
public class Archer extends Unit{
public Archer() {
name = "弓箭手";
// 用弓箭攻击
fightBeHavior = new FightWithArrow();
// 回血速度一般
selfHealingBeHavior = new SelfHealingNormal();
}
}
public class Knight extends Unit{
public Knight() {
name = "骑士";
// 用长枪攻击
fightBeHavior = new FightWithSpear();
// 回血速度快
selfHealingBeHavior = new SelfHealingFast();
}
}
public class Catapult extends Unit{
public Catapult() {
name = "投石车";
// 用石头攻击
fightBeHavior = new FightWithStone();
// 不能回血,不用设置回血功能
}
}
测试类
public class TestUnit {
public static void main(String[] args) {
// 步兵出击
Unit unit = new Infantry();
unit.move();
unit.fight();
unit.stop();
unit.selfHealing();
// 弓箭手出击
unit = new Archer();
unit.move();
unit.fight();
unit.stop();
unit.selfHealing();
// 骑士出击
unit = new Knight();
unit.move();
unit.fight();
// 骑士扔掉了长枪,拔出了宝剑
System.out.println("动态设置:骑士扔掉了长枪,拔出了宝剑!");
unit.setFightBeHavior(new FightWithSword());
unit.fight();
unit.stop();
unit.selfHealing();
// 投石车出击
unit = new Catapult();
unit.move();
unit.fight();
unit.stop();
// 修理工给投石车做了保养,投石车也能回血了
System.out.println("动态设置:修理工给投石车做了保养,投石车也能回血了");
unit.setSelfHealingBeHavior(new SelfHealingSlow());
unit.selfHealing();
}
}
测试结果
我是:步兵
向前挺进...
攻击:看我的剑法
原地待命...
我每秒回血1%
我是:弓箭手
向前挺进...
攻击:我射箭-->
原地待命...
我每秒回血2%
我是:骑士
向前挺进...
攻击:看我的长枪
动态设置:骑士扔掉了长枪,拔出了宝剑!
攻击:看我的剑法
原地待命...
我每秒回血3%
我是:投石车
向前挺进...
攻击:我扔石头
原地待命...
动态设置:修理工给投石车做了保养,投石车也能回血了
我每秒回血1%
“setFightBeHavior(FightBeHavior fi)方法和setSelfHealingBeHavior(SelfHealingBeHavior se)方法,能在程序运行中动态的切换实现的算法,这是策略模式的一大优点。“,老王补充道。
小帅看着代码,陷入了沉思,真是越看越有味道。
“原来FightBeHavior接口就是兵器库(算法库),以后不管用到什么其他的战斗方式,只要在实现类里增加就行了,就像在兵器库(算法库)里增加兵器(算法),比如:双节棍,大刀,流星锤等等,如果有新的兵种直接拿来用就行了,实现了类的复用。
战斗方式可以随时替换,并且不会影响到具体兵种的使用。“小帅貌似明白了策略模式的精髓所在,显得有点兴奋。
老王点点头,开心的笑了,“就是这样啊,你已经学会策略模式了”。
代码链接