IGame游戏公司的故事

1.1 讨论会
话说有一个叫IGame的游戏公司,正在开发一款ARPG游戏(动作&角色扮演类游戏,如魔兽世界、梦幻西游这一类的游戏)。一般这类游戏都有一个基本的功能,就是打怪(玩家攻击怪物,借此获得经验、虚拟货币和虚拟装备),并且根据玩家角色所装备的武器不同,攻击效果也不同。这天,IGame公司的开发小组正在开会对打怪功能中的某一个功能点如何实现进行讨论,他们面前的大屏幕上是这样一份需求描述的ppt:

各个开发人员,面对这份需求,展开了热烈的讨论,下面我们看看讨论会上都发生了什么。
1.2 实习生小李的实现方式
在经过一番讨论后,项目组长Peter觉得有必要整理一下各方的意见,他首先询问小李的看法。小李是某学校计算机系大三学生,对游戏开发特别感兴趣,目前是IGame公司的一名实习生。
经过短暂的思考,小李阐述了自己的意见:
“我认为,这个需求可以这么实现。HP当然是怪物的一个属性成员,而武器是角色的一个属性成员,类型可以使字符串,用于描述目前角色所装备的武器。角色类有一个攻击方法,以被攻击怪物为参数,当实施一次攻击时,攻击方法被调用,而这个方法首先判断当前角色装备了什么武器,然后据此对被攻击怪物的HP进行操作,以产生不同效果。”
而在阐述完后,小李也飞快的在自己的电脑上写了一个Demo,来演示他的想法,Demo代码如下。

package wangxing20200905_1_1;
/*
 * 怪物类
 */
public class GuaiWu {
	//定义怪物名称的成员变量
	private String gw_name;
	//定义怪物生命值的成员变量
	private int gw_hp;
	public GuaiWu() {}
    public GuaiWu(String name,int hp) {
    	this.gw_name=name;
    	this.gw_hp=hp;
    }
	public String getGw_name() {
		return gw_name;
	}
	public void setGw_name(String gw_name) {
		this.gw_name = gw_name;
	}
	public int getGw_hp() {
		return gw_hp;
	}
	public void setGw_hp(int gw_hp) {
		this.gw_hp = gw_hp;
	}
	public void getInfo () {
		System.out.println(gw_name+"  "+gw_hp);
	}	
	}
package wangxing20200905_1_1;
/*
 * 角色类
 */
public class JueSe {
	//定义角色名称
	private String js_name;
	//定义角色所持武器
	private String js_wuqi;
	public  JueSe(String name, String wuqi){
	    this.js_name=name;
	    this.js_wuqi=wuqi;
		
	}
	public String getJs_name() {
		return js_name;
	}
	public void setJs_name(String js_name) {
		this.js_name = js_name;
	}
	public String getJs_wuqi() {
		return js_wuqi;
	}
	public void setJs_wuqi(String js_wuqi) {
		this.js_wuqi = js_wuqi;
	}
	public void getIn() {
		System.out.println(js_name+" "+js_wuqi);
	}
	/*
	 * 攻击怪物
	 */
	public void goji(GuaiWu gw) {
		//判断怪物死活
		if(gw.getGw_hp()<=0) {
		System.out.println(gw.getGw_name()+"已经死亡了");
		}else {
		//判断角色的装备
			if(js_wuqi.equals("木剑")) {
				gw.setGw_hp(gw.getGw_hp()-50);
			System.out.println(js_name+"打"+gw.getGw_name()+"1次,怪物损失了50hp,还剩"+gw.getGw_hp()+"hp");
			}
			if(js_wuqi.equals("铁剑")) {
				gw.setGw_hp(gw.getGw_hp()-100);
			System.out.println(js_name+"打"+gw.getGw_name()+"1次,怪物损失了100hp,还剩"+gw.getGw_hp()+"hp");
			}
			if(js_wuqi.equals("魔剑")) {
			//得到一个随机数
			int random=((int)(Math.random()*10))+1;
			if(random>5) {
			gw.setGw_hp(gw.getGw_hp()-400);
			System.out.println(js_name+"打"+gw.getGw_name()+"1次,产生暴击率,怪物损失了400hp,还剩"+gw.getGw_hp()+"hp");
			}else {
				 gw.setGw_hp(gw.getGw_hp()-200);
					System.out.println(js_name+"打"+gw.getGw_name()+"1次,怪物损失了200hp,还剩"+gw.getGw_hp()+"hp");
				}
			}
		}
	}
}
package wangxing20200905_1_1;
public class Zhu {
	public static void main(String[] args) {
	//创建怪物
	GuaiWu g1=new GuaiWu("小怪",500);
	GuaiWu g2=new GuaiWu("河蟹",1000);
	//创建角色
	JueSe je1=new JueSe("小兵","木剑");
	JueSe je2=new JueSe("跑车","铁剑");
	JueSe je3=new JueSe("英雄","魔剑");
	g1.getInfo();
	g2.getInfo();
	je1.getIn(); 
	je2.getIn(); 
	je3.getIn(); 
	je1.goji(g1);
	je2.goji(g1);
	je3.goji(g2);
	}
}

运行结果

1.3 架构师的建议
小李阐述完自己的想法并演示了Demo后,项目组长Peter首先肯定了小李的思考能力、编程能力以及初步的面向对象分析与设计的思想,并承认小李的程序正确完成了需求中的功能。但同时,Peter也指出小李的设计存在一些问题,他请小于讲一下自己的看法。
小于是一名有五年软件架构经验的架构师,对软件架构、设计模式和面向对象思想有较深入的认识。他向Peter点了点头,发表了自己的看法:
“小李的思考能力是不错的,有着基本的面向对象分析设计能力,并且程序正确完成了所需要的功能。不过,这里我想从架构角度,简要说一下我认为这个设计中存在的问题。
首先,小李设计的Role类的Attack方法很长,并且方法中有一个冗长的if…else结构,且每个分支的代码的业务逻辑很相似,只是很少的地方不同。
再者,我认为这个设计比较大的一个问题是,违反了OCP原则。在这个设计中,如果以后我们增加一个新的武器,如倚天剑,每次攻击损失500HP,那么,我们就要打开Role,修改Attack方法。而我们的代码应该是对修改关闭的,当有新武器加入的时候,应该使用扩展完成,避免修改已有代码。
一般来说,当一个方法里面出现冗长的if…else或switch…case结构,且每个分支代码业务相似时,往往预示这里应该引入多态性来解决问题。而这里,如果把不同武器攻击看成一个策略,那么引入策略模式(Strategy Pattern)是明智的选择。
最后说一个小的问题,被攻击后,减HP、死亡判断等都是怪物的职责,这里放在Role中有些不当。”
Tip:OCP原则,即开放关闭原则,指设计应该对扩展开放,对修改关闭。
Tip:策略模式,英文名Strategy Pattern,指定义算法族,分别封装起来,让他们之间可以相互替换,此模式使得算法的变化独立于客户。
小于边说,边画了一幅UML类图,用于直观表示他的思想。

Peter让小李按照小于的设计重构Demo,小李看了看小于的设计图,很快完成。相关代码如下:

package wangxing20200905_2_1;
/*
 * 攻击接口
 */
public interface GongJi {
	//打怪方法
	void daguai(GuaiWu  gw) ;	
	}

package wangxing20200905_2_1;
/*
 * 怪物类
 */
public class GuaiWu {
	//定义怪物名称的成员变量
	private String gw_name;
	//定义怪物生命值的成员变量
	private int gw_hp;
	public GuaiWu() {}
    public GuaiWu(String name,int hp) {
    	this.gw_name=name;
    	this.gw_hp=hp;
    }
	public String getGw_name() {
		return gw_name;
	}
	public void setGw_name(String gw_name) {
		this.gw_name = gw_name;
	}
	public int getGw_hp() {
		return gw_hp;
	}
	public void setGw_hp(int gw_hp) {
		this.gw_hp = gw_hp;
	}
	/*
	 * 怪物自身的掉血操作
	 */
	public void diaoxue(int hp) {
	//判断怪物的死亡状态
		if(this.gw_hp<=0) {
			System.out.println("怪物"+this.getGw_name()+"已经死亡");
			return;
		}
		this.gw_hp=this.gw_hp-hp;
		if(this.gw_hp<=0) {
			System.out.println("怪物"+this.getGw_name()+"已经死亡");
		}else {
			System.out.println("怪物"+this.gw_name+"损失"+hp+"hp"+"剩余"+this.gw_hp+"hp,仍可以继续攻击");
		}
	}
	public void getInfo () {
		System.out.println(gw_name+"  "+gw_hp);
	}	
	}
package wangxing20200905_2_1;
/*
 * 角色类
 */
public class JueSe {
	//定义角色名称
	private String js_name;
	//定义角色所持武器
	private GongJi js_wuqi;
	public  JueSe(String name, GongJi wuqi){
	    this.js_name=name;
	    this.js_wuqi=wuqi;
		
	}
	public String getJs_name() {
		return js_name;
	}
	public void setJs_name(String js_name) {
		this.js_name = js_name;
	}
	public GongJi getJs_wuqi() {
		return js_wuqi;
	}
	public void setJs_wuqi(GongJi js_wuqi) {
		this.js_wuqi = js_wuqi;
	}
	public void getIn() {
		System.out.println(js_name+" "+js_wuqi);
	}
	/*
	 * 攻击怪物
	 */
	public void goji(GuaiWu gw) {
		this.js_wuqi.daguai(gw);
				}		
		}
package wangxing20200905_2_1;
public class MoJian implements GongJi {
	public void daguai(GuaiWu gw) {
	   int random=((int)(Math.random()*10))+1;
	   if(random>5) {
		   System.out.println("产生暴击!!");
		   gw.diaoxue(400);
	   }else {
		   gw.diaoxue(200);
	   }
	}
}

package wangxing20200905_2_1;
public class MuJian implements GongJi {
	public void daguai(GuaiWu gw) {
		gw.diaoxue(50);
	}
}

package wangxing20200905_2_1;
public class TieJian implements GongJi{
	public void daguai(GuaiWu gw) {	
		gw.diaoxue(100);
	}
}

package wangxing20200905_2_1;
public class TieJian implements GongJi{
	public void daguai(GuaiWu gw) {	
		gw.diaoxue(100);
	}
}

运行结果

编译运行以上代码,得到的运行结果与上一版本代码基本一致。
1.4 小李的小结
Peter显然对改进后的代码比较满意,他让小李对照两份设计和代码,进行一个小结。小李简略思考了一下,并结合小于对一次设计指出的不足,说道:
“我认为,改进后的代码有如下优点:
第一,虽然类的数量增加了,但是每个类中方法的代码都非常短,没有了以前Attack方法那种很长的方法,也没有了冗长的if…else,代码结构变得很清晰。
第二,类的职责更明确了。在第一个设计中,Role不但负责攻击,还负责给怪物减少HP和判断怪物是否已死。这明显不应该是Role的职责,改进后的代码将这两个职责移入Monster内,使得职责明确,提高了类的内聚性。
第三,引入Strategy[策略]模式后,不但消除了重复性代码,更重要的是,使得设计符合了OCP。如果以后要加一个新武器,只要新建一个类,实现IAttackStrategy接口,当角色需要装备这个新武器时,客户代码只要实例化一个新武器类,并赋给Role的Weapon成员就可以了,已有的Role和Monster代码都不用改动。这样就实现了对扩展开发,对修改关闭。”
Peter和小于听后都很满意,认为小李总结的非常出色。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值