一、讨论会

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


  各开发人员,面对这份需求,展开了热烈的讨论,下面看看在讨论会上都发生了什么。

二、实习生小贾的实现方式

  在经过一番讨论后,项目组长Jindi觉得有必要整理一下各方的意见,他首先询问小贾的看法。小贾是某学校计算机系大三学生,对游戏开发特别感兴趣,目前是Game公司的一名实习生。

  经过短暂的思考,小贾阐述了自己的意见:

  “我认为,这个需求可以这么实现。HP当然是怪物的一个属性成员,而武器是角色的一个属性成员,类型可以使字符串,用于描述目前角色所装备的武器。角色类有一个攻击方法,以被攻击怪物为参数,当实施一次攻击时,攻击方法被调用,而这个方法首先判断当前角色装备了什么武器,然后据此对被攻击怪物的HP进行操作,以产生不同效果。”

  而在阐述完后,小贾也飞快的在自己的电脑上写了一个Demo,来演示他的想法,Demo代码如下。

package com.game.test1;
/*** 怪物类* @author Administrator**/
public class GuaiWu {//定义一个保存怪物名称的变量private  String  gw_name;//定义一个保存怪物血量的变量private  int  gw_hp;public GuaiWu() {}public GuaiWu(String  gw_name,int  gw_hp) {this.gw_name=gw_name;this.gw_hp=gw_hp;}//提供怪物名称的set/get方法public String getGw_name() {return gw_name;}public void setGw_name(String gw_name) {this.gw_name = gw_name;}//提供怪物生命值的set/get方法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 com.game.test1;
/*** 角色类* @author Administrator**/
public class JueSe {//定义一个保存角色的名称private  String js_name;//定义一个保存武器的变量private  String  wuqi;public  JueSe(String js_name,String  wuqi) {this.js_name=js_name;this.wuqi=wuqi;}public String getJs_name() {return js_name;}public void setJs_name(String js_name) {this.js_name = js_name;}public String getWuqi() {return wuqi;}public void setWuqi(String wuqi) {this.wuqi = wuqi;}public  void  getJsInfo() {System.out.println(js_name+" "+wuqi);}/*** 打怪物*/public void  daGuai(GuaiWu gw) {//判断怪物是否死亡if(gw.getGw_hp()<=0) {System.out.println(gw.getGw_name()+":已经死亡!");}else {//判断角色装备if(wuqi.equals("木剑")) {gw.setGw_hp(gw.getGw_hp()-20);System.out.println(js_name+" 打"+gw.getGw_name()+"1次,怪物损失20hp,还剩"+gw.getGw_hp()+"hp");}//判断角色所持有的武器,从而对怪物进行操作if(wuqi.equals("铁剑")) {gw.setGw_hp(gw.getGw_hp()-50);System.out.println(js_name+" 打"+gw.getGw_name()+"1次,怪物损失50hp,还剩"+gw.getGw_hp()+"hp");}if(wuqi.equals("魔剑")) {//得到一个随机数int random=((int)(Math.random()*10))+1;if(random>5) {gw.setGw_hp(gw.getGw_hp()-200);System.out.println("产生暴击!!!");System.out.println(js_name+" 打"+gw.getGw_name()+"1次,怪物损失200hp,还剩"+gw.getGw_hp()+"hp");}else {gw.setGw_hp(gw.getGw_hp()-100);System.out.println(js_name+" 打"+gw.getGw_name()+"1次,怪物损失100hp,还剩"+gw.getGw_hp()+"hp");}}}}
}
package com.game.test1;public class Test {public static void main(String[] args) {GuaiWu  gw1=new GuaiWu("小妖",500);GuaiWu  gw2=new GuaiWu("大妖",1500);gw1.getInfo();gw2.getInfo();JueSe  js1=new JueSe("小剑客", "木剑");JueSe  js2=new JueSe("剑魔", "魔剑");js1.getJsInfo();js2.getJsInfo();js1.daGuai(gw1);js2.daGuai(gw2);}}

运行结果:

三、架构师的建议

  小贾阐述完自己的想法并演示了Demo后,项目组长Jindi首先肯定了小贾的思考能力、编程能力以及初步的面向对象分析与设计的思想,并承认小贾的程序正确完成了需求中的功能。但同时,Jindi也指出小贾的设计存在一些问题,他请闻哥讲一下自己的看法。

  闻哥是一名有五年软件架构经验的架构师,对软件架构、设计模式和面向对象思想有较深入的认识。他向Jindi点了点头,发表了自己的看法:

  “小贾的思考能力是不错的,有着基本的面向对象分析设计能力,并且程序正确完成了所需要的功能。不过,这里我想从架构角度,简要说一下我认为这个设计中存在的问题。

  首先,小贾设计的 角色 类的 打怪 方法很长,并且方法中有一个冗长的if…else结构,且每个分支的代码的业务逻辑很相似,只是很少的地方不同。

  再者,我认为这个设计比较大的一个问题是,违反了OCP原则。在这个设计中,如果以后我们增加一个新的武器,如倚天剑,每次攻击损失500HP,那么,我们就要打开JueSe ,修改daGuai方法。而我们的代码应该是对修改关闭的,当有新武器加入的时候,应该使用扩展完成,避免修改已有代码。

  一般来说,当一个方法里面出现冗长的if…else或switch…case结构,且每个分支代码业务相似时,往往预示这里应该引入多态性来解决问题。而这里,如果把不同武器攻击看成一个策略,那么引入策略模式(Strategy Pattern)是明智的选择。

  最后说一个小的问题,被攻击后,减HP、死亡判断等都是怪物的职责,这里放在Role中有些不当。”
  Tip:OCP原则,即开放关闭原则,指设计应该对扩展开放,对修改关闭。

  Tip:策略模式,英文名Strategy Pattern,指定义算法族,分别封装起来,让他们之间可以相互替换,此模式使得算法的变化独立于客户。

  闻哥边说,边画了一幅UML类图,用于直观表示他的思想。


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

package com.game.test1;
/*** 角色类* @author Administrator**/
public class JueSe {//定义一个保存角色的名称private  String js_name;//定义一个保存武器的变量private  GongJiInterface  wuqi;public  JueSe(String js_name,GongJiInterface  wuqi) {this.js_name=js_name;this.wuqi=wuqi;}public String getJs_name() {return js_name;}public void setJs_name(String js_name) {this.js_name = js_name;}public GongJiInterface getWuqi() {return wuqi;}public void setWuqi(GongJiInterface wuqi) {this.wuqi = wuqi;}public  void  getJsInfo() {System.out.println(js_name+" "+wuqi);}/*** 打怪物*/public void  daGuai(GuaiWu gw) {this.wuqi.gongji(gw);}
}
package com.game.test1;
/*** 怪物类* @author Administrator**/
public class GuaiWu {//定义一个保存怪物名称的变量private  String  gw_name;//定义一个保存怪物血量的变量private  int  gw_hp;public GuaiWu() {}public GuaiWu(String  gw_name,int  gw_hp) {this.gw_name=gw_name;this.gw_hp=gw_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;}/*** 怪物自身的掉血操作* @param hp*/public  void  diaoxue(int hp) {//判断怪物的死活if(this.gw_hp<=0) {System.out.println("怪物-"+this.gw_name+",已经死亡!");return;}this.gw_hp=this.gw_hp-hp;if(this.gw_hp<=0){System.out.println("怪物\""+this.gw_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 com.game.test1;
/*** 攻击接口* @author Administrator**/
public interface GongJiInterface {//打怪方法void gongji(GuaiWu  gw);
}
package com.game.test1;
//木剑
public class MuJian implements GongJiInterface{@Overridepublic void gongji(GuaiWu gw) {gw.diaoxue(20);}}
package com.game.test1;
//铁剑
public class TieJian implements GongJiInterface{@Overridepublic void gongji(GuaiWu gw) {gw.diaoxue(50);}}
package com.game.test1;
//魔剑
public class MoJian implements GongJiInterface{@Overridepublic void gongji(GuaiWu gw) {int random=((int)(Math.random()*10))+1;if(random>5) {System.out.println("产生暴击!!");gw.diaoxue(200);}else {gw.diaoxue(100);}}}
package com.game.test1;
//倚天剑
public class YiTianJian implements GongJiInterface{@Overridepublic void gongji(GuaiWu gw) {gw.diaoxue(1200);}}
package com.game.test1;public class Test {public static void main(String[] args) {GuaiWu  gw1=new GuaiWu("小妖",500);GuaiWu  gw2=new GuaiWu("大妖",1500);gw1.getInfo();gw2.getInfo();JueSe  js1=new JueSe("小剑客", new MuJian());JueSe  js2=new JueSe("剑魔", new YiTianJian());js1.getJsInfo();js2.getJsInfo();js1.daGuai(gw1);js2.daGuai(gw2);}}

运行结果:

四、小贾的总结

  Jindi显然对改进后的代码比较满意,他让小贾对照两份设计和代码,进行一个小结。小贾简略思考了一下,并结合闻哥对一次设计指出的不足,说道:

  “我认为,改进后的代码有如下优点:

  第一,虽然类的数量增加了,但是每个类中方法的代码都非常短,没有了以前Attack方法那种很长的方法,也没有了冗长的if…else,代码结构变得很清晰。

  第二,类的职责更明确了。在第一个设计中,Role不但负责攻击,还负责给怪物减少HP和判断怪物是否已死。这明显不应该是Role的职责,改进后的代码将这两个职责移入Monster内,使得职责明确,提高了类的内聚性。

  第三,引入Strategy[策略]模式后,不但消除了重复性代码,更重要的是,使得设计符合了OCP。如果以后要加一个新武器,只要新建一个类,实现IAttackStrategy接口,当角色需要装备这个新武器时,客户代码只要实例化一个新武器类,并赋给Role的Weapon成员就可以了,已有的Role和Monster代码都不用改动。这样就实现了对扩展开发,对修改关闭。”

  Jindi和闻哥听后都很满意,认为小贾总结的非常出色。

Game游戏公司的故事相关推荐

  1. 15.IGame游戏公司的故事

    1 讨论会 话说有一个叫IGame的游戏公司,正在开发一款ARPG游戏(动作&角色扮演类游戏,如魔兽世界.梦幻西游这一类的游戏).一般这类游戏都有一个基本的功能,就是打怪(玩家攻击怪物,借此获 ...

  2. 依赖注入那些事儿【1】 之 IGame游戏公司的故事

    1.1 讨论会 话说有一个叫IGame的游戏公司,正在开发一款ARPG游戏(动作&角色扮演类游戏,如魔兽世界.梦幻西游这一类的游戏).一般这类游戏都有一个基本的功能,就是打怪(玩家攻击怪物,借 ...

  3. 如何在三个月内创立一家估值200亿的游戏公司?

    很长一段时间,游戏公司一直是土豪的代名词,据说某游戏公司给一位主策的年终奖是一套价值千万的房子(真实案例),有越来越多创业者投身到游戏领域中来,抱着"做一年然后套现卖掉"的想法开始 ...

  4. 故事向|在3A游戏公司工作多年的3D建模师自述,制作3A大作经验心得(上)

    今天给大家带来一篇教程,感谢charleshuang715同学的翻译. 来自艺夺蒙特利尔工作室的Charlotte Delannoy 将分享她在制作古墓丽影系列里关于贴图以及材质优化方面的经验,以及给 ...

  5. 男子欲刺杀游戏公司创始人被抓,还是游戏里“兔女郎”惹的祸?

    一款游戏,受玩家欢迎追捧,对于游戏公司来说是好事,但是惹得玩家太过痴迷也有较大风险,甚至创始人都要因旗下游戏面临生命威胁. 4月24日晚,有网传消息称,一男子持刀刺欲杀米哈游两位创始人,结果在米哈游公 ...

  6. 回顾社交游戏公司Zynga创业史

    原帖: http://gamerboom.com/archives/44239 作者:Dean Takahashi 在短短的5年时间里,Zynga便在电子游戏领域里掀起了巨大的热潮.即将迎来重要的IP ...

  7. 游戏公司岗位揭密:美工易搭讪,策划都很屌

    最近,由一个越南人写出来的无聊小游戏<像素鸟>极度火爆,许多人被它变态的难度虐到直想摔手机.实际上,这是个关于程序猿如何报复世界的故事.而在游戏公司内,各个岗位之间彼此交错,构成了一副错综 ...

  8. 游戏公司都有什么岗位?主要做什么的?

    游戏公司都有什么岗位?主要做什么?一个游戏开发的大体流程是策划先提出主要的设计思路和设计内容,然后将设计想法与程序美术沟通,三方达成一致之后,最后进入制作. 本文将按照互联网公司标配的三大职位来进行说 ...

  9. 游戏公司岗位大揭秘!各职位工作都有什么特点?

    相信很多人都爱玩游戏,也在游戏世界中得到很多乐趣.既然那么多人爱玩游戏,那么在以游戏谋生的人一定不在少数.那么你了解游戏公司的各个职位的工作情况吗?这些职位的工作内容都是什么? 我们先来看一下游戏开发 ...

最新文章

  1. Windows 8的圣战
  2. 剑桥少儿英语预备级教案(上) unit15 I can draw it.
  3. 海淘会不会成为电商的下一片蓝海?
  4. some screenshot for SAP Fiori smart template resource load
  5. linux mdev -s没有运行,mdev详解
  6. mysql临时表怎么显示_如何查看MySQL临时表的说明?
  7. 查看linux cpu和内存利用率__linux top命令
  8. 华为堡垒机_案例:任正非曾为小灵通痛苦8到10年,促进了华为终端公司诞生
  9. hplus java_Java HijrahDate plus(long, TemporalUnit)用法及代码示例
  10. html代码学习离线文档,新手学HTML代码的简易方法
  11. 1人30天44587行代码,分享舍得网开发经验
  12. 重积分 | 高斯公式使用的四种情况
  13. dis的前缀单词有哪些_三个常见的前缀,re-,anti-,dis-,你知道它们的用法吗?
  14. “云界十年”——第十届中国云计算大会举行
  15. Web前端系列技术之移动端CSDN会员页面复刻(动态完整版)
  16. 因子完备数c语言,编写函数输出完备数及其所有约数
  17. 迷你旅游网——旅游线路用名称查询以及查看详情(完成查询条件不确定性的定义模板方式解决...)
  18. 中国移动光猫的拨号和桥接模式的区别
  19. android 怎么封装方法,开发那点事(九)安卓开发,封装常用方法,工作用
  20. 初学MSP430F5529时钟以及FLL配置

热门文章

  1. 黑马程序员C++笔记——STL泛型编程VS2019
  2. 数字图像处理——直方图累计
  3. 把UTF-8编码转换为GB2312编码
  4. oracle官方最新补丁,最新oracle 补丁下载
  5. 基于javaee的餐馆在线预订餐系统
  6. win10英雄联盟登录服务器未响应,win10系统lol英雄联盟登录服务器未响应的恢复技巧...
  7. 用 Python 分析《红楼梦》(1)
  8. Window 10 使用WSL2下载编译Android 10 系统源码,并用sourceInsight 4 看系统源码
  9. 终结研发业务的永恒互怼,我们做了这个需求漏斗
  10. java 清空string_java - 如何清除或清空StringBuilder?