1 讨论会
话说有一个叫IGame的游戏公司,正在开发一款ARPG游戏(动作&角色扮演类游戏,如魔兽世界、梦幻西游这一类的游戏)。一般这类游戏都有一个基本的功能,就是打怪(玩家攻击怪物,借此获得经验、虚拟货币和虚拟装备),并且根据玩家角色所装备的武器不同,攻击效果也不同。这天,IGame公司的开发小组正在开会对打怪功能中的某一个功能点如何实现进行讨论,他们面前的大屏幕上是这样一份需求描述的ppt:
各个开发人员,面对这份需求,展开了热烈的讨论,下面我们看看讨论会上都发生了什么。
2 实习生小李的实现方式在经过一番讨论后,项目组长Peter觉得有必要整理一下各方的意见,他首先询问小李的看法。小李是某学校计算机系大三学生,对游戏开发特别感兴趣,目前是IGame公司的一名实习生。经过短暂的思考,小李阐述了自己的意见:
“我认为,这个需求可以这么实现。HP当然是怪物的一个属性成员,而武器是角色的一个属性成员,类型可以使字符串,用于描述目前角色所装备的武器。角色类有一个攻击方法,以被攻击怪物为参数,当实施一次攻击时,攻击方法被调用,而这个方法首先判断当前角色装备了什么武器,然后据此对被攻击怪物的HP进行操作,以产生不同效果。”而在阐述完后,小李也飞快的在自己的电脑上写了一个Demo,来演示他的想法,Demo代码如下。
package com.wangxing.game.test1;
public class GuaiWu {
//怪物名称
private String guaiwu_name;
//怪物生命值
private int guaiwu_hp;
public String getGuaiwu_name() {
return guaiwu_name;
}
public void setGuaiwu_name(String guaiwu_name) {
this.guaiwu_name = guaiwu_name;
}
public int getGuaiwu_hp() {
return guaiwu_hp;
}
public void setGuaiwu_hp(int guaiwu_hp) {
this.guaiwu_hp = guaiwu_hp;
}
}
package com.wangxing.game.test1;
public class JueSe {
//武器变量
private String wuqi;
//通过构造方法设置武器数据值
public JueSe(String wq){
this.wuqi=wq;
}
public void gongji(GuaiWu gw){
//判断怪物是否死亡,如果已经死亡,则无序继续攻击
if(gw.getGuaiwu_hp()<=0){
System.out.println("怪物-"+gw.getGuaiwu_name()+"-已经死亡!");
return;
}
//根据角色装配的武器不同,对怪物产生不同的效果
//木剑攻击效果
if(this.wuqi.equals("木剑")){
//得到当前怪物的生命值
int dangqianhp=gw.getGuaiwu_hp();
//对当前生命值减20
int lessafterhp=dangqianhp-20;
//重新将损失以后的生命值赋值给怪物
gw.setGuaiwu_hp(lessafterhp);
//继续判断怪物是否死亡
if(gw.getGuaiwu_hp()<=0){
System.out.println("怪物-"+gw.getGuaiwu_name()+"-已经死亡!");
return;
}else{
System.out.println("怪物-"+gw.getGuaiwu_name()+"-损失20hp,还剩"+gw.getGuaiwu_hp()+"hp!");
}
}
//铁剑攻击效果
if(this.wuqi.equals("铁剑")){
//得到当前怪物的生命值
int dangqianhp=gw.getGuaiwu_hp();
//对当前生命值减50
int lessafterhp=dangqianhp-50;
//重新将损失以后的生命值赋值给怪物
gw.setGuaiwu_hp(lessafterhp);
//继续判断怪物是否死亡
if(gw.getGuaiwu_hp()<=0){
System.out.println("怪物-"+gw.getGuaiwu_name()+"-已经死亡!");
return;
}else{
System.out.println("怪物-"+gw.getGuaiwu_name()+"-损失50hp,还剩"+gw.getGuaiwu_hp()+"hp!");
}
}
//魔剑攻击效果
if(this.wuqi.equals("魔剑")){
//得到当前怪物的生命值
int dangqianhp=gw.getGuaiwu_hp();
//对当前生命值减100/暴击200
int random=((int)(Math.random()*10))+1;
int lesshp=0;
if(random>5){
System.out.println("产生暴击!!!");
lesshp=200;
}else{
lesshp=100;
}
int lessafterhp=dangqianhp-lesshp;
//重新将损失以后的生命值赋值给怪物
gw.setGuaiwu_hp(lessafterhp);
//继续判断怪物是否死亡
if(gw.getGuaiwu_hp()<=0){
System.out.println("怪物-"+gw.getGuaiwu_name()+"-已经死亡!");
return;
}else{
System.out.println("怪物-"+gw.getGuaiwu_name()+"-损失"+lesshp+"hp,还剩"+gw.getGuaiwu_hp()+"hp!");
}
}
}
}
//测试类
package com.wangxing.game.test1;
public class TestMain {
public static void main(String[] args) {
//创建怪物对象
GuaiWu gw1=new GuaiWu();
gw1.setGuaiwu_name("巡山小妖");
gw1.setGuaiwu_hp(1000);
//创建角色对象
JueSe js1=new JueSe("木剑");
JueSe js2=new JueSe("铁剑");
JueSe js3=new JueSe("魔剑");
while(gw1.getGuaiwu_hp()>0){
//角色攻击怪物
js3.gongji(gw1);
}
}
}
3 架构师的建议
小李阐述完自己的想法并演示了Demo后,项目组长Peter首先肯定了小李的思考能力、编程能力以及初步的面向对象分析与设计的思想,并承认小李的程序正确完成了需求中的功能。但同时,Peter也指出小李的设计存在一些问题,他请小于讲一下自己的看法。
小于是一名有五年软件架构经验的架构师,对软件架构、设计模式和面向对象思想有较深入的认识。他向Peter点了点头,发表了自己的看法:
“小李的思考能力是不错的,有着基本的面向对象分析设计能力,并且程序正确完成了所需要的功能。不过,这里我想从架构角度,简要说一下我认为这个设计中存在的问题。
首先,小李设计的角色类的攻击方法很长,并且方法中有一个冗长的if…else结构,且每个分支的代码的业务逻辑很相似,只是很少的地方不同。
再者,我认为这个设计比较大的一个问题是,违反了O【Open】C【Close】P原则。在这个设计中,如果以后我们增加一个新的武器,如倚天剑,每次攻击损失500HP,那么,我们就要打开角色类,修改攻击方法。而我们的代码应该是对修改关闭的,当有新武器加入的时候,应该使用扩展完成,避免修改已有代码。
一般来说,当一个方法里面出现冗长的if…else或switch…case结构,且每个分支代码业务相似时,往往预示这里应该引入多态性来解决问题。而这里,如果把不同武器攻击看成一个策略,那么引入策略模式(Strategy Pattern)是明智的选择。
最后说一个小的问题,被攻击后,减HP、死亡判断等都是怪物的职责【高内聚】,这里放在角色中有些不当。”
小于边说,边画了一幅UML类图,用于直观表示他的思想
Peter让小李按照小于的设计重构Demo,小李看了看小于的设计图,很快完成。相关代码如下:
package com.wangxing.game.test2;
public class GuaiWu {
//怪物名称
private String guaiwu_name;
//怪物生命值
private int guaiwu_hp;
public String getGuaiwu_name() {
return guaiwu_name;
}
public void setGuaiwu_name(String guaiwu_name) {
this.guaiwu_name = guaiwu_name;
}
public int getGuaiwu_hp() {
return guaiwu_hp;
}
public void setGuaiwu_hp(int guaiwu_hp) {
this.guaiwu_hp = guaiwu_hp;
}
public void notify(int lesshp){
//判断怪物是否死亡
if(this.guaiwu_hp<=0){
System.out.println("怪物-"+this.guaiwu_name+"-已经死亡!");
return;
}
//设置怪物掉血
this.guaiwu_hp=this.guaiwu_hp-lesshp;
//判断怪物是否死亡
if(this.guaiwu_hp<=0){
System.out.println("怪物-"+this.guaiwu_name+"-已经死亡!");
return;
}else{
System.out.println("怪物-"+this.guaiwu_name+"-损失"+lesshp+"hp,还剩"+this.guaiwu_hp+"hp!!!");
}
}
}
package com.wangxing.game.test2;
public interface AttackStrategyInterface {
//攻击怪物方法
void AttackTarget(GuaiWu gw);
}
package com.wangxing.game.test2;
public class JueSe {
//定义角色装配的武器
public AttackStrategyInterface weapon;
public void Attack(GuaiWu gw){
weapon.AttackTarget(gw);
}
}
package com.wangxing.game.test2;
public class IronSword implements AttackStrategyInterface{
@Override
public void AttackTarget(GuaiWu gw) {
gw.notify(20);
}
}
package com.wangxing.game.test2;
public class WoodSword implements AttackStrategyInterface{
@Override
public void AttackTarget(GuaiWu gw) {
gw.notify(50);
}
}
package com.wangxing.game.test2;
public class MagicSword implements AttackStrategyInterface{
@Override
public void AttackTarget(GuaiWu gw) {
int random=((int)(Math.random()*10))+1;
int lesshp=(random>5)?200:100;
if(lesshp==200){
System.out.println("产生暴击!!!");
}
gw.notify(lesshp);
}
}
//测试
package com.wangxing.game.test2;
public class TestMain {
public static void main(String[] args) {
GuaiWu gw=new GuaiWu();
gw.setGuaiwu_name("银角大王");
gw.setGuaiwu_hp(10000);
JueSe js1=new JueSe();
//js1.weapon=new IronSword();
//js1.weapon=new WoodSword();
//js1.weapon=new MagicSword();
js1.weapon=new YiTianJian();
while(gw.getGuaiwu_hp()>0){
js1.Attack(gw);
}
}
}
Tip:OCP原则,即开放关闭原则,指设计应该对扩展开放,对修改关闭。
Tip:策略模式,英文名Strategy Pattern,指定义算法族,分别封装起来,让他们之间可以相互替换,此模式使得算法的变化独立于客户。


![15.IGame游戏公司的故事[20220624] 15.IGame游戏公司的故事[20220624]](http://www.mshxw.com/aiimages/31/993137.png)
