- 浏览: 121453 次
- 性别:
- 来自: 北京
最新评论
-
javaWorm123:
超赞 谢谢啦
Flex与Flash组合开发最佳实践__Combain Flex&Flash -
cwfmaker:
有同样经历的人路过!
我爱你 种过 -
zhuxiangyan:
哈哈,大学就是让人消磨时间,锻炼一个人的耐力的。
四年如果能熬 ...
毕业了,想写点什么又不知从何说起 -
iamzealotwang:
<div class="quote_title ...
游戏设计-Chapter2 -
geek87:
很好,很强大。受教了。。
游戏设计-Chapter2
涉及方面:面向对象思想在实际编程之中的运用
摘要:观察者模式
1·观察者模式其实是对象和对象之间的一种交流方式
2·时刻要记得对象在不断的变化
呼~终于弄完了。在开始介绍"观察者模式"之前我先说点题外话。
说实话我最初实在看Hibernate源码,发现自己对观察者模式理解的不够,于是想回来补补课的。其实写Chapter1的时候,几乎都是照搬《HeadFirst》的设计模式,只是把鸭子转成游戏的角色了。呵呵
我也没有想到会有人关注这篇Blog,其实写的目的真的就是写给自己读读好加深印象。从文章的内容也可以看出来,只是想到哪里就写哪里,没有照顾读者的感受。(因为我预期的是只有自己看嘛 呵呵)
现在发现问题严重了很多,当我想给原来的例子加上"观察者模式的时候"我突然发觉已经无法再把他当成例子来对待了。
我发觉设计模式不再是设计模式了
就好比观察者模式 我现在已经完全把它当成是一个人的表达自己说的话。
我原来本是在编游戏,后来不知不觉转成了j2ee(就像这次看Hibernate源码事件一样)。在做网站的时候也用设计模式,甚至包括架构模式。什么MVC,什么SessionFactory。那个时候随便问一个人问我,我能很清楚的告诉他什么是MVC,什么是工厂模式。不过我发觉现在我对设计模式的理解有了很大的改变。我不知道我这种改变是对oo认识的加深,还是我自己的胡乱想象。
所以我恳请:
如果您对我的文章有兴趣。读完后帮着顶一下,或者踩一脚。不要顾及对我的面子,觉得踩人家不好。
如果您觉得我说得很有问题,希望在踩完后也能给予我一些意见。我在这里先谢过您了。
Ok,在我带您认识我的"孩子"之前,还是需要简单介绍一下 什么是书上说的观察者模式。
/** *观察者 */ public Obervice { public void update(){ System.out.println("收到消息"); } } /** *主题 */ public Subject { private List obervice = new ArrayList(0); //储存所有的观察者 //注册观察者 public void registerObserver(Obervice o){ obervice.add(o); } //移除观察者 public void removeObserver(Obervice o){ obervice.remove(o); } //通知观察者 public void notifyObserver(){ Iterator it = obervice.iterator; while(it.hasNext()){ it.next().update(); } } } public static void main(String[] arg){ Obervice o = new Obervice(); Subject s = new Subject; s.registerObserver(o); s.notifyObserver(); }
结果:
收到消息
程序是我在写字板里面手写的,所以可能需要改改小错误才能run起来。不过如果您对这个模式还不是很了解。建议您应该先看一下HeadFirst关于这章的解释然后再接着往下看。
好的,既然您读到这里了,我就假设您已经了解了观察者模式了,那下面我就要说说我对观察者模式的理解了。
经过这2天多的不断使用,我现在已经不把它当作"观察者模式"了。
浅显的说可以把其当作两个人,一个人在说,一个人在听。
还记得HeadFirst里面那个小闹剧么?
我来和您一起回忆一下:
里面有一个猎头(我给起的 Petter),两个软件人员(Ron,Jill)。
于是这场短剧就开始了:
Ron说: 猎头,我想找份Java工作。
Jill说: 猎头,我想找份C++工作。
Petter把他们的名字添加在了自己的小本本上面。
Petter拿起电话,按照本本上的名字从头到尾打了一遍。告诉他们同一件事情:
我这里有份C++编程的工作。
好,短剧先说到这里,让我们来分析一下:
对于Petter,Ron,Jill他们都是什么?Object没有问题吧。
Ron和Jill这两个Object只要关心自己的事情就好了。
Ron在家打着Dota,突然Petter给它打了一个电话:我这里有份C++编程的工作。Ron想了想,Petter有病吧,我又没要C++工作。于是挂断电话继续玩儿Dota去了
Jill呢,当听到Petter给自己打电话时候很高兴。于是去应聘C++工作去了。
Object之所以这样,因为这样耦合性最小。Petter(Object1)只管按照本本上的电话记录打电话即可,其他的不用关心。Ron(Object2)和Jill(Object3)也是这样做自己的事情,听到了Petter的电话。想想和自己有关无关,无关接着做自己的事情就好了。
那很容易的就可以吧Ron和Jill理解为有了耳朵,耳朵给予了他们听的功能。
而Petter有了嘴巴,嘴巴给予了他说得功能。
这样每个人就独立了起来,他们各自干各自的活儿。
*****插入图片1
很完美是吧?不过现实总是残酷的,让我们继续把故事将下去
*****插入图片2
啊哦,人心叵测啊。原来Ron不光有耳朵,而且有嘴巴啊。
让我们再回头看图片1:
除了图片有点傻,你还发现了什么?对!他们都是残疾人。
(注意要用唱两只老虎的语调来念后面的话^_^)一个只有嘴巴,一个只有耳朵,真奇怪,真奇怪。
是吧,我们的程序中不可能只存在3个对象或则只存在这些残疾人的。
如果你把对象当成人的话,而更大的对象或者说整个程序就是这些人组合在一起完成的一件工作,对不?人和人之间是需要交流的,Class和Class之间当然也不例外了啊。我把两个人做成连体婴儿了,那交流是最方便的了。不过这样的耦合度必然也是最高的了,往往你把连体婴儿分开他们总是不能活的好好的。那Ron,Jill,Petter这种关系好不好呢?两个哑巴和一个聋子。当整个程序中只有这三个对象的时候,且两个哑巴只关心同意见事情的时候显然没有问题。聋子告诉他们了消息,一个说谢谢,一个说你神经病。即使聋子没有听见也没有问题,问题依旧可以解决了。不过当对象多起来的时候,这种光听不说,或者这种光说不听的同志显然要给大家拉后腿的。那我们需要什么样的人呢?健全人嘛!这个大家自然能想到了。
当对象都是健全人的时候,完美了是吧?现实又一次摧残了我们。。。
为啥?因为健全人就需要操心更多事情。
Petter原来是聋子,所以不用关心Ron说他是神经病,Jill对他说谢谢。
现在他要想一想 "神经病?","谢谢" 恩看来这个不是要向我订阅职位广告的人。
想到解决方法了?好吧,你很聪明。
当人与人之间可以交流的时候,有时候会出现这种场景。一对情侣和朋友一起走在大街上。女孩对男孩说"我爱你",男孩对女孩说"我也爱你"。于是两个人热情的开始接吻。这时候他的朋友走了过来也对男孩说"我爱你"。男孩怎么办?男孩的观察对象不仅有女孩,而且有他的朋友(因为他们一起出去玩儿的。注意:男孩并不关心大街上那两条狗,及他不关系和他不认识的人)。而当两个人都说了"我爱你"的时候,男孩怎么作出反映呢?
显然上面的程序是不对的吧(请往上看看之前的Obervice和Subject类)
public static void main(String[] arg){ Obervice boy = new Obervice(); //男孩有耳朵 //他要对女孩即朋友说得话作出响应 Subject girlFriend = new Subject; Subject normalFriend = new Subject; boy.registerObserver(girlFriend); boy.registerObserver(normalFriend); girlFriend.notifyObserver(); normalFriend.notifyObserver(); System.out.println("出麻烦喽"); }
显然您已经想到了解决方法,暂时没有?不用着急Java已经替我们完成了这些工作。(详情请看HeadFirst里面相关介绍)
好吧,现在回头看看我们都解决了什么问题:
1·我们解决了处理不必要的消息的问题(神经病,谢谢 问题)
2·我们解决了处理相同语句的问题(我爱你 问题)
完美了是吧? 哦 No! do not Say That..
在此我就省略一下关于 现实世界 的那句话。但是确实有一些比上面这个问题更棘手的问题需要我们解决。不过这些问题就不能离开代码来空谈了。我们要正式的继续设计我们这个游戏Baby了(不了解的建议先看一下Chapter1)
首先我希望现在咱俩已经达成了一种共识。
听、说、这两个方法应该是两种能力。所以我只能说具有这种能力,而不能像Monstar extends Creautre 说 我也extends了听或者说(注意,我想成为正常人,所以我要有这两种能力。而不是其中的一种 因为extends只能有其中的一种能力)
好吧我们肯定要把Subject和Obervice变成接口。是不?(注:我开始认为Java中是以类方法实现的,不过我刚才试了一下Java中现在也可以以接口方法实现)
/** * 观察着模式之中的观察方,实现更新接口 * @author Tunied Nmetal * j2me.zor.org * */ public interface Obervice { /** * Object sender为告知接受方此消息是谁发送的 * 所有观察者都需要判断自己是否需要接收该发送方的消息 * int updateType为告知接受方发送的是何种更新消息 * 所有观察者都需要 用 final class UpdateType之中的数据和updateTyep之中的数据比较(代码重构前暂时不需要) * 以判断自己应该做什么时候能够作出正确的响应 * Object value为发送过去的得值 * 当需要使用数据的时候,观察者对value进行适当的转型来接收相应的数据 * @param sender * @param valueType * @param value */ public void update(Object sender,int valueType,Object value); } /** * 观察着模式中的被观察方,实现添加、移除观察者,以及通知观察者接口 * @author Tunied Nmetal * j2me.zor.org * */ public interface Subject { public void registerObserver(Obervice o); //注册观察者 public void removeObserver(Obervice o); //移除观察者 public void notifyObserver(); //通知观察者 }
现在让我们开始一场崔斯特与大怪物之间的殊死搏斗吧。战斗一般是怎么进行的呢?
首先让角色上场好了
Role 崔斯特 = new Role();
Monstar 地精 = new Monstar();
战斗开始了!
崔斯特.Attack(地精);
地精一看,情况不妙。急忙又找来了2个兄弟
Monstar 哥布林 = new Monstar();
Monstar 小蜘蛛 = new Monstar();
于是崔斯特对所有人发起了攻击
崔斯特.Attack(地精);
崔斯特.Attack(哥布林);
崔斯特.Attack(小蜘蛛);
哥布林一看,不行啊。打不过,我也要叫人!
....
Monstar 哥布林2 = new Monstar();
崔斯特一想,这么多人,干脆别一个个打了。我放个火球术好了。
什么?崔斯特还会火球术?我那本书上不是这么写的。拜托! 现在是我在写书好不好。。那好吧。。。
Role 会火球术的崔斯特 = new Role();
会火球术的崔斯特.克隆值(崔斯特);
....崔斯特会火球术确实不好,那还是让崔斯特叫出关法海来协助作战吧^_^ Oh~No!
*(*&*(&&……*战斗被迫停止了。
所以,我们需要重新认识一下角色这个问题了。角色什么老在变?
攻击方式在变。
我可以用弓箭进攻,我可以用双刀。我更可以施放魔法! 好的,还有呢?
防御方式在变。
我可以穿布甲,我可以穿锁子甲。我甚至可以开坦克。。。(好吧,崔斯特一下成重装机兵。)不过你说得对,这些都会变。
我想您一定还能想出好多,其实我也想出出好多来了。不过那个时候代码已经改了三遍了(就是因为这个才耗了这么长时间),我可能需要再进一步认识一下角色,对代码进行再次重构了。
不过在此之前 让咱们假设常会变的只有这两样好么?
好的,如果咱们达成了共识 我想继续。
那我下一个问题是:在这些会变的单元里面什么不会变?
这块可能需要好好想想了,这里我先发表自己的看法。多种多样的攻击方式以及防御方式之中。
即使我在显示器上面的图标变了又变,伤害值加了又加,属性伤害添了又填。不变是什么?
不变的是那种行为,我用剑也好,弓也好。我砍你也好劈你也好,我对你做了些什么?
我攻击你了^_^
你穿上皮夹克,你开上大坦克。你又干嘛了?
你防御了我这次攻击了。
所以说,攻击这种行为,防御这种行为是不会变的。
Ok,回顾一下,看看我们又达成了什么共识。
1·我们必要将攻击,防御提出来单独设计,因为其总在变化(想想火球术)
2·之所以称木剑可用来攻击,坦克可用来防御。是因为其具有攻击,防御这两种属性。
好的,既然这样,我们是不是可以这么认为呢:
+++++插入图片3
啊哦,除了比较屎的图片你还发现了啥?
恩,你发现了图上一下自有了6个对象,并且他们之间是有关联的。 (and more~~)
你在给我介绍观察者模式,所以他们之间一定是用观察者模式进行交流!
好吧,让我们重新回到刚才的战斗。
首先角色上场
Role 崔斯特 = new Role();
Monstar 地精 = new Monstar();
崔斯特这个时候说:开始前,咱们先达成个协议
//我攻击你的具有防御属性的东西,而不直接攻击你
崔斯特.getAttack().set观察者(地精.getDefence())
地精一想很不错嘛,公平起见。我也攻击你的防御性东西
地精.getAttack().set观察者(崔斯特.getDefence())
好了达成协议了!战斗开始吧。
崔斯特.getAttack().Attack(); //崔斯特大喊我攻击完啦
地精的具有防御属性的东西(比如烂衣服)听到了这个消息。恩~有人攻击我了,我来看看。伤害有100点,我自己能抗住10点
于是烂衣服高喊:臭地精!你受到90点伤害。地精听到了想了想,我有100格血,受到90点伤害。还有10点血。一点都不好玩儿!我要叫同伴
Monstar 哥布林 = new Monstar();
此时崔斯特和也和这个哥布林攀谈起来。咱俩也打成个协议好不?协议?好吧。
崔斯特.getAttack().set观察者(哥布林.getDefence());
哥布林.getAttack().set观察者(崔斯特.getDefence());
达成协议后,崔斯特心想。这么多怪,我要打多变天啊。干脆来个火球术好了
崔斯特.setAttack(new 火球术Attack());
崔斯特.getAttack().Attack();
让我们先忘掉哥布林,回到地精这里。地精的烂衣服有一次受到了攻击的消息。恩~又有人攻击我了,我来看看。伤害有5000点,我自己能抗住10点
于是烂衣服高喊:臭地精!你受到4990点伤害。地精听到了想了想,我有10格血,受到4990点伤害。还有...总之我死了!于是地精死前高喊:我死啦!
烂衣服听到了这个消息赶忙说:我的主人死啦,别攻击我啦。崔斯特的 火球术Attack() 听到了这个消息 对自己的主人说:主人您刚才让我攻击的对象死了,这是他的尸体
于是崔斯特解到尸体取走了100点经验值(注:由于得到的是对象,就是之前所说的Objcet value,所以也就是得到了 (哥布林)value,当以后扩充以后不仅可以得到经验值,更可以得到哥布林相关的一切一切东西)。崔斯特取走了100点经验值后,发觉自己升级了。于是大喊:我升级啦!火球术Attack()听到主人升级了,于是增加了自己的攻击力...
战斗就这样完美的结束了。。。。
战斗结束了,我所阐述的关于"观察者模式"的理解也蕴含在这里面了。
我想把剩下的时间留给您自己,发挥您的想象 我想您现在应该可以做以下一些事情的一些或所有
1·确定是顶我一下还是踩我一下 (怕...怕...)
2·看一下我给出的源码,自己Debug运行一遍。我源码给出了注释,因为代码改动比较多,所以有些注释可能并不是其所表达的那样。忘海涵。
3·加入我的圈子,进一步讨论游戏设计相关的知识 http://gameart.group.iteye.com/(请先阅读置顶文章)
4·给我提出改进建议,和我一起共同完成咱们的"GameBaby"
5·最重要的,如果您认同我所讲的"观察者模式"结合您自己的领域,体会其用法,并分享给大家。我愿意做其中的一个观察者。只要您这个主题让我订阅的话 呵呵
好吧,到此。一切都完美了么? NO~~又来了.....
过场话我就不说了(写了3个小时了,没精力了呵呵) 我给大家说一个我犯的错误,以及这个错误的解决方法。 看代码先:
这个是Defence类: /** * 当宿主死亡且宿主是怪物类 * 把宿主传送出去 * 并调用清理函数 */ if (valueType == Language.Creature_Monstar_Dead ) { for(int i = 0; i < Obervice.size(); i++){ Obervice obervice = (Obervice)Obervice.get(i); obervice.update(sender,Language.Behavior_Defense_MonstarDead,sender); //此处需特别注意!如果BeAttack类得知宿主死亡,且宿主是Monstar类 //则将怪物类(此处为传进来的Sender)传送出去,并且告诉监听者它已经死亡了 } removeObserver((Obervice)sender); //把宿主从自己的观察者队列里面移除,因为其死亡了 /** * 判断自己的观察者里面是否还有自己的宿主 * 如果没有了自己的宿主则清理自己 */ Iterator it = Obervice.iterator(); boolean isNoCreature = true; while(it.hasNext()){ /** * 如果还存在生物宿主则false且返回 */ if(GameTools.isExtends(it.next(), Creature.class)){ //这是我自己写的小工具,判断it.next()是否是一个 Creature.class //(此工具可以判断abstract类) 如果您对该工具有任何更改建议请及时通知我,我将万分感激 isNoCreature = false; break; } } if(isNoCreature){ CleanMyself(); //执行清理函数,好让GC清理自己 } } }
如果您忍的住的话,请只看代码考虑5分钟,如果您能够想明白问题出在哪里。我觉得您对观察者思想的造诣已经超越我太多太多了。我自己是想了好久,Debug好久才弄明白怎么回事的。
我的想法是这样的:
1·Defence类收到了自己宿主死亡的消息,首先把这个消息告诉自己所有观察者。我的宿主死亡啦!
2·接着Defence判断自己是否还有其他的宿主(这里现在没有必要,不过那个时候我考虑可能会和共体技能有关(两个人共享伤害,War3白牛的技能))
3·发现自己没有宿主了,调用清理函数,清理自己。
没有问题?呵呵 把问题抽象一些 看图:
++++++插入图片4+++++++++++
B是A的观察者,C是B的观察者,D是C的观察者,同时D也是B的观察者。
A发送消息告诉B
B发送消息告诉C
C发送消息告诉D
D接收到消息,此时清理的了自己
C已经发送完所有的观察者,返回
B发送消息给D
系统发送消息给你 NullPointExcption -。-
为啥?因为电脑的函数调用是栈式调用。你过早的清理了自己。所以上面必然在调用你时候必然会抛出异常。
那时候一定要清理自己呢?留给Gc干这些事情好不好?请注意!游戏中的死亡不是现实中Java对象的死亡。我的血量<0了,所以我认为自己死亡了。不代表Gc也认为我死亡了。
所以你总应该做一些事情好让Gc去回收你。具体怎么做?先自己想想,没准你比我有更好的解决办法呢。如果想知道我是怎么解决的。看代码中死神类(Azrael.class)都干了些什么
好了,完....饿....没力气闹了。
接着看代码:
还是Defence类: /** * 当Defense收到由Attack发送过来的:你被攻击了 *将发送方传过来的value(Attack对象自己)所造成的伤害削减后 * 传给自己的我给观察者。告诉他们:我被攻击了 */ if (valueType == Language.Behavior_Attack_Attack) { Attack attacker = (Attack) value; int type = attacker.getType(); // 取得攻击对象的攻击类型 /** * 判断自己受到伤害的类型 按物理系以及魔法系给出不同的受伤公式 */ switch (type) { case AttackType.ATK: attacker.setDemage(attacker.getDemage() - DEF); break; case AttackType.MATK: attacker.setDemage(attacker.getDemage() - MDEF); } for (int i = 0; i < Obervice.size(); i++) { Obervice obervice = (Obervice) Obervice.get(i); obervice.update(this, Language.Behavior_Defense_BeAttack, attacker); } }
这块做的事情已经写得很清楚了,受到攻击消减完再把消息传递出去。可是当你Junit代码的时候,你会发现崔斯特没攻击一次怪物,自己的攻击力就下降了点
百思不得其解,最后又是跟踪了半天代码才明白了这点。并且这个我觉得是很重要得放。
抛开游戏的不谈,在您使用观察者模式的时候可能也会出现这种情况:
观察方对主题方的数据进行了变动,而您并不是想要这样做的。那该怎么办?
还是看代码吧 呵呵。我的思路来源自UDP数据包。我把观察者模式理解成计算机之间的不同节点。
好了就这么多了。用最后的力气才说几句废话。
1·如果您觉得我写得没用,请务必踩我一脚啊。我好下次不用写这么多东西了 感觉都快成写小说的了-。-
2·如果你对游戏开发有兴趣,记得来我的圈子啊。先阅读置顶帖哦 http://gameart.group.iteye.com/group/admin
3·感谢一下所有在Chapter1里面回复的人。真的很感谢,你们的支持是我最大的东西
4·特别感谢一下40020072,没想到你对我的代码这么上心。 谢谢,谢谢。
感谢所有看完这篇Blog的人,不用看,您就是其中一位。谢谢,谢谢了。
关于代码的任何修改,批评,指责,谩骂,问题 等等等等 任何有关的事情,请一定联系我。
谢谢
评论
不好意思 这些都是原来很早时候写的,现在回过头来看有许多想法都不不对。请不要太拿这些东西当回事儿。
关于设计模式,我之前打算也是想一边看书一边来用。不过就像我C2中提到的,我现在已经不认为“设计模式”是单纯的设计模式了。
我认为设计模式 有些是人和人打交道的一种方式,有些则是一些社会现象。
之所以这么想,我是把每个类都看成了一个人。而一个程序就是一群人完成的一件工程。
好,回到观察者模式来。
我来说说我对观察者模式的理解。
我认为观察者模式是两种能力。什么能力呢?一种说的能力,一种是听的能力。
你所说的对象和对象之间的关系 是类似于这样的。
老师对班长说 明天开会,
班长对组长说明天带着你们组员7:30来301教室
组长对所有的组员说:明天7:00宿舍门口集合。
组员听到了这个消息。
而我说的对象与对象之间的关系类似于双向之间的交流。
你人不认为类再抽象化,或者追溯类的由来。 可以追溯或者抽象化为函数?
你说得函数就是每个函数都是void类型的,我说的函数是有返回值类型的。
而
这句话我也认同,不过我认为和我这种想法没有冲突。
add类 就是进行相加操作。 但这个不能说add只“听”而不“说”吧。
加法是一种加工,那是否有必要单一到把整数加法和小数加法分开的地步呢?
这个讨论吧,就好像讨论那种“资本主义好”还是“社会主义好”这种问题,我觉得很难有个结果。
对于你说得那种观察者模式,完全可以。对于类的扩展以及改变都可以。
而我说这种观察者模式,我也认为我也做到了扩展改变等操作。
我没有学过设计模式,包括23个设计模式我只知道“观察者”,“装饰者”,“工厂模式”。别的我什么都不知道,可能就是由于我不知道这些设计模式的原因所以我才没有被限制在“设计模式”里面。 我现在会先好好把其余的设计模式看一遍,然后把我这套“理论”好好整理整理。付出与实际,用我这个GameTime 好好检验检验。实践是检验真理的最好方法,不是么? 呵呵
希望到时候能和你继续讨论。
谢谢
再提一些我的拙见~
1、首先软件开发有一条很重要的原则---单一职责原则(SRP):就一个类而言,应该仅有一个职责(职责:引起变化的原因a reason for change)。所以一个类同时作为主题和观察者是不合适的,解决办法可以是再写一对新的“主题与观察者”,前一个观察者(含有新主题的引用)收到通知时通知新主题,新主题再通知它的观察着们。
2、我们可以把掉血、死亡、获得经验、升级等等看做是一些行为,分别再提取成一组行为。然后让生物(包括人和怪NPC等)具有这些行为,例如如下的掉血行为分为物理伤害掉血和魔法伤害掉血.可以看做是一个鸭子能飞(含几种飞的方式)、会游泳(几种游泳的方式)。
多用组合,少用继承
针对接口编程,而不是针对实现
/* * 掉血行为 * @author msn:ever@live.cn */ public interface LostHp { public int lostHp(int ATK,int MATK,int HpNow); }
/* * 受到物理伤害的行为 */ public class LostHpByATK implements LostHp{ public int lostHp(int ATK, int MATK, int HpNow) { //可加入更多细节算法例如根据防御值等 //剩余血量 int hp = HpNow - ATK; return hp; } }
/* * 受到魔法技能伤害的行为 */ public class LostHpByMATK implements LostHp { public int lostHp(int ATK, int MATK, int HpNow) { //可加入更多细节算法例如根据防御值等 //剩余血量 int hp = HpNow - MATK; return hp; } }
/** * @author msn:ever@live.cn 角色类 extends 生物类 */ public class Role extends Creature { //经验 private int exp; //玩家具有一个掉血的行为(分为物理伤害掉血和魔法伤害掉血) private LostHp lostHp; //也可以具有一个死亡的行为,例如WOW中的战场死亡或者野外死亡副本死亡等等此处略 /* * 受到物理伤害损失血的行为 * 委托给行为类 */ public int lostHpByATK(int ATK,int MATK, int HpNow) { lostHp = new LostHpByATK(); int hp = lostHp.lostHp(ATK, MATK, HpNow); return hp; } /* * 受到魔法伤害损失血的行为 * 委托给行为类 */ public int lostHpByMATK(int ATK,int MATK, int HpNow) { lostHp = new LostHpByMATK(); int hp = lostHp.lostHp(ATK, MATK, HpNow); return hp; } public Role(String name, int hp, int mp, int exp,boolean isDead) { super(); this.hp = hp; this.mp = mp; this.name = name; this.exp = exp; this.isDead = isDead; } public int getExp() { return exp; } public void setExp(int exp) { this.exp = exp; } public String toString() { return new StringBuilder().append("角色:" + getName()).append( "\nHp: " + getHp()).append("\nMp: " + getMp()).append( "\n经验: " + getExp()).toString(); } }
/* * 实现被观察者(主题)接口 * 攻击方 */ public class Attack implements Subject { // 记录观察者 private ArrayList<Obervice> obervices ; DemagePacket demagePacket; //初始化 public Attack() { obervices = new ArrayList<Obervice>(); }; public Attack(DemagePacket demagePacket) { obervices = new ArrayList<Obervice>(); this.demagePacket = demagePacket; } //注册观察者 public void registerObserver(Obervice o) { obervices.add(o); } //删除观察者 public void removeObserver(Obervice o) { int index = obervices.indexOf(o); if (index >= 0) obervices.remove(index); } //通知观察者 public void notifyObserver() { for(int i = 0; i <obervices.size();i++) { Defense obervice = (Defense)obervices.get(i); obervice.update(demagePacket); } } }
/* * 实现观察者接口 * 被攻击方 */ public class Defense implements Obervice { private DemagePacket demagePacket; public void update(DemagePacket demagePacket) { this.demagePacket = demagePacket; } //打印信息 public void disPlay() { System.out.println( "受到:基本物理伤害:" + demagePacket.getDemage() + " 技能名称:" + demagePacket.getAttackSkill().getSkillName() + " 魔法技能伤害:" + demagePacket.getAttackSkill().getSkillDemage()); } //获取此次攻击的基本物理伤害 public int getATK() { return demagePacket.getDemage(); } //获取此次攻击的魔法技能伤害 public int getMATK() { return demagePacket.getAttackSkill().getSkillDemage(); } //获取此次攻击的技能伤害包 public DemagePacket getDemagePacket() { return demagePacket; } }
/* * 测试类 */ public class DemageTest extends TestCase { private Role role; private AttackSkills attackSkills; DemagePacket demagePacket; Attack attack; Defense defense; // Defense defense1; // Defense defense2; // Defense defense3; // 初始化 public void setUp() throws Exception { role = new Role("崔特特", 90, 80, 0,false); // 技能伤害 20 attackSkills = new MagicSkill("魔法攻击技能", 20); // 物理伤害 10 demagePacket = new DemagePacket(10, attackSkills); attack = new Attack(demagePacket); defense = new Defense(); // defense1 = new Defense(); // defense2 = new Defense(); // defense3 = new Defense(); System.out.println("===============测试开始============"); } public void tearDown() throws Exception { System.out.println("===============测试结束============"); } /** * 目的:测试一个攻击场景 * 怪物攻击玩家 */ public void testRoleAttack() { //打印玩家资料 System.out.println(role.toString()); //把一个观察者(这里是---被攻击者---玩家)注册到主题(这里是---攻击者---怪物)中 attack.registerObserver(defense); //通知观察者 attack.notifyObserver(); //观察者打印 defense.disPlay(); //调用玩家掉血的行为--受到普通攻击是10 int hp1 = role.lostHpByATK(defense.getATK(), defense.getMATK(), role.getHp()); role.setHp(hp1); //调用玩家掉血的行为--受到魔法攻击是20 int hp2 = role.lostHpByMATK(defense.getATK(), defense.getMATK(), role.getHp()); role.setHp(hp2); //打印玩家剩余血量 System.out.println("剩余血量为:" + role.getHp()); //判断是否死亡,可以提取为role的行为,例如WOW中的战场死亡或者野外死亡副本死亡等等 if(role.getHp() == 0) { role.setDead(true); } //测试此时玩家血量是否剩余60 assertEquals(60, role.getHp()); }
其它类见下面的回复
测试打印:
===============测试开始============
角色:崔特特
Hp: 90
Mp: 80
经验: 0
受到:基本物理伤害:10 技能名称:魔法攻击技能 魔法技能伤害:20
剩余血量为:60
===============测试结束============
以上是我的一些看法,希望iamzealotwang和大家多多指正
另外希望iamzealotwang还是回到设计模式和设计原则的运用,不用关注太多细节。 :)
C1就很经典~
代码下载在http://everlive.iteye.com/admin/blogs/232504
哦,还有 关于这点 我认为很有意思。还是想请你说明一下 呵呵。
生物的死亡是比较好判断的,主要是类似于Attack和Defence类,这两个类是成对出现的。在你的单独观察者模式里面,Attack只能"说"话,而不能听话。对于这种对象有什么好的办法让它“死亡”呢?
哦 by the way 我没看见你role的代码,不知道你role代码是什么样子的。关于怪物死了需要获得经验值这个问题时候是怎么处理的呢?
你的例子我仔细看了。
首先说一些关于这篇文章要表达的意思
我的意思是想告诉大家关于普通"观察者模式"的另外一些东西。比如你代码之中的Attack类和Defence类他们都单纯的实现了观察者,或者被观察者。而很多问题往往不能保证观察者不是被观察者。也就是说一个class既要向别人"说"一些话,同时也要"听"别人说一些话。
而当一个class同时具备了这两项能力以后就会出现一些小问题了。这些问题我会在C3里面继续讨论的。
至于Attack和Defence这个我认为我的代码的确有一些问题的,咱们先不讨论。呵呵。我对于这个脑子里面有些想法,不过现在很混乱 没有整理好成套的思路。
咱们先讨论一下:
1·是否存在一个class即为观察者又为被观察者情况。正如HeadFirst里面那个男的(Ron)
2·如果你同意1,那么对于这种情况的设计方式应该怎么样?
这里提一些我的看法以及我修改的代码。
1、首先希望楼主还是以代码为主要表达方式:代码是最好的文档.
2、楼主的Attack.java和Defense.java写的很混乱,它们都实现了主题与观察者的接口,这样不是一个很好的设计,另外里面包含许多变化的东西,我们应该把变化的东西抽象出来。
下面是我做的设计和修改:
被观察者 -- 攻击者
观察者 -- 被攻击者
因为实际中可以1个玩家攻击多个怪物,也会有1个怪物攻击多个玩家。
/** * 观察者模式中的被观察方(主题),实现添加、移除观察者,以及通知观察者接口 */ public interface Subject { public void registerObserver(Obervice o); //注册观察者 public void removeObserver(Obervice o); //移除观察者 public void notifyObserver(); //通知观察者 }
/* * 实现被观察者(主题)接口 * 攻击方 */ public class Attack implements Subject { // 记录观察者 private ArrayList<Obervice> obervices ; DemagePacket demagePacket; //初始化 public Attack() { obervices = new ArrayList<Obervice>(); }; public Attack(DemagePacket demagePacket) { obervices = new ArrayList<Obervice>(); this.demagePacket = demagePacket; } //注册观察者 public void registerObserver(Obervice o) { obervices.add(o); } //删除观察者 public void removeObserver(Obervice o) { int index = obervices.indexOf(o); if (index >= 0) obervices.remove(index); } //通知观察者 public void notifyObserver() { for(int i = 0; i <obervices.size();i++) { Defense obervice = (Defense)obervices.get(i); obervice.update(demagePacket); } } }
/** * 观察着模式之中的观察方,实现更新接口 */ public interface Obervice { public void update(DemagePacket demagePacket); }
/* * 实现观察者接口 * 被攻击方 */ public class Defense implements Obervice { private DemagePacket demagePacket; public DemagePacket getDemagePacket() { return demagePacket; } public void setDemagePacket(DemagePacket demagePacket) { this.demagePacket = demagePacket; } public void update(DemagePacket demagePacket) { this.demagePacket = demagePacket; } public void disPlay() { System.out.println( "受到:基本伤害:" + demagePacket.getDemage() + " 技能名称:" + demagePacket.getAttackSkill().getSkillName() + " 技能伤害:" + demagePacket.getAttackSkill().getSkillDemage()); } }
/** * 伤害数据包对象,目前只包括基本伤害值,技能伤害类 */ public class DemagePacket { private int demage; //攻击时候产生的原始伤害 private AttackSkills attackSkill; //攻击的种类 public DemagePacket(int demage, AttackSkills attackSkill) { super(); this.demage = demage; this.attackSkill = attackSkill; } public int getDemage() { return demage; } public void setDemage(int demage) { this.demage = demage; } public AttackSkills getAttackSkill() { return attackSkill; } public void setAttackSkill(AttackSkills attackSkill) { this.attackSkill = attackSkill; } }
/* * 技能伤害超类 */ public class AttackSkills { //技能名称、技能伤害 protected String skillName; protected int skillDemage; public AttackSkills(String skillName,int skillDemage) { this.skillName = skillName; this.skillDemage = skillDemage; } public String getSkillName() { return skillName; } public void setSkillName(String skillName) { this.skillName = skillName; } public int getSkillDemage() { return skillDemage; } public void setSkillDemage(int skillDemage) { this.skillDemage = skillDemage; } }
/** * 魔法技能类 extends AttackSkills */ public class MagicSkill extends AttackSkills { /* * 初始化技能名称 技能伤害 */ public MagicSkill(String skillName, int skillDemage) { super(skillName, skillDemage); } public String toString() { String s = "技能名称:" + this.getSkillName() + " 技能伤害:" + this.skillDemage; return s; } }
/* * 测试类 */ public class DemageTest extends TestCase { private Role role; private AttackSkills attackSkills; DemagePacket demagePacket; Attack attack; Defense defense; Defense defense1; Defense defense2; Defense defense3; // 初始化 public void setUp() throws Exception { role = new Role("崔特特", 90, 80, 0); attackSkills = new MagicSkill("魔法攻击技能", 10); // 假设基本伤害和玩家血量有关,以后再扩展力量敏捷等等 demagePacket = new DemagePacket(role.getHp() / 2, attackSkills); attack = new Attack(demagePacket); defense = new Defense(); defense1 = new Defense(); defense2 = new Defense(); defense3 = new Defense(); System.out.println("===============测试开始============"); } public void tearDown() throws Exception { System.out.println("===============测试结束============"); } /** * 目的:测试角色初始化,技能初始化以及攻击一只怪物 * 一个观察者被更新 */ public void testRoleAttack() { System.out.println(role.toString()); attack.registerObserver(defense); attack.notifyObserver(); defense.disPlay(); //测试一次攻击的伤害是否是55 = 基本伤害(hp/2) + 技能伤害 assertEquals(55, defense.getDemagePacket().getDemage() + defense.getDemagePacket().getAttackSkill().getSkillDemage()); } /** * 目的:测试角色初始化,技能初始化以及攻击三只怪物 * 三个观察者被更新 */ public void testRoleAttackThree(){ System.out.println(role.toString()); attack.registerObserver(defense1); attack.registerObserver(defense2); attack.registerObserver(defense3); attack.notifyObserver(); defense1.disPlay(); defense2.disPlay(); defense3.disPlay(); } }
以上是我的见解,希望大家给予指正。
我认为“死亡”可以提取为一个类,加入生物属性里,时间关系先不写了。主要关注“观察者模式”
楼主打算用设计模式这书做个游戏吧..
源码
相关推荐
"香港朗文教案1A-Chapter2-Nice-to-meet-you.pdf" 本资源为香港朗文教案1A-Chapter 2 Nice to meet you!的教学设计,旨在培养学生在日常生活中介绍家人和朋友的能力,并掌握相关的英语表达。 教学目标: 1. 教...
Java面向对象编程(Object-Oriented Programming,简称OOP)是一种强大的编程范式,它将复杂的程序设计问题通过模拟现实世界中的对象来解决。在Java中,OOP主要包含四个核心概念:封装、继承、多态和抽象。下面将...
- Chapter 2: Appeasing the Tiki Gods - 这可能是对开发中会遇到的问题和挑战的调侃表达,强调学习和解决这些问题的重要性。 - Chapter 3: Handling Basic Interaction - 讲述了用户界面的基础交互,包括按钮、滑动...
Java以其简洁性、安全性、可移植性和高性能等特性,广泛应用于软件开发、移动应用、Web应用程序、企业级应用、游戏开发等多个领域。本章节我们将深入学习Java的第七章内容,这通常是关于高级特性和进阶主题的讨论。 ...
开发者可以学习到游戏开发的最佳实践,而学生和爱好者则可以通过实际项目来加深对编程和游戏设计的理解。 总的来说,《Aftermath - Chapter One: Charred Ash》的开源性质不仅让玩家能够体验到一个独特且富有深度的...
在游戏应用中,模型可能涉及关卡设计、玩家分数和怪物位置等。处理数据的业务规则或领域逻辑通常在这个层次进行,虽然这些术语听起来复杂,但实质上就是指如何操作和管理数据。 2. View对象:视图层是用户界面的...
《VC网络游戏设计与实现》是一份关于利用Visual C++(简称VC)开发网络游戏的源代码集合,涵盖了多个关键章节的内容。这份资源对于那些希望深入理解网络游戏背后的编程原理和实践技术的开发者来说,是非常宝贵的参考...
- **灵活性与适应性**: 通用系统设计时考虑了多种用途和未来的扩展性,例如个人电脑需兼顾多媒体播放、游戏、办公等多种需求。 - **性能与价格**: 用户在购买通用系统时通常期望获得高性能配置,以满足当前及未来...
"Windows游戏编程大师技巧Chapter2"聚焦于提升开发者的技术水平,特别是针对编译过程中遇到的问题提供了详细的解决策略。本章内容涵盖了一系列核心概念和技术,旨在帮助程序员克服在游戏开发过程中可能遇到的挑战。 ...
压缩包中的"chapter7"可能包含的是书中关于第七章的内容,可能是关于游戏中的战斗系统、AI设计或者特定的图形渲染技术。具体章节内容需要解压后查看,以获取更详细的学习资料。 总之,通过这个C++ RPG游戏程序设计...
本篇章将深入探讨XNA 4.0的核心特性、设计理念以及如何通过它来构建一个完整的游戏项目。 一、XNA 4.0简介 XNA 4.0是XNA Framework的最新版本,它支持Windows Phone 7和Zune设备,以及Xbox 360。XNA的全称是"XNA ...
2. **Chapter 6**: 这一章可能涉及到游戏对象(如角色、敌人和物品)的管理和生命周期。这包括对象创建、销毁、状态管理以及AI(人工智能)的基础知识。AI系统可能包含简单的路径寻找和敌人的行为模式设计。 3. **...
2. **DirectInput**: 这是DirectX中的另一个关键组件,用于获取来自键盘、鼠标、游戏手柄等输入设备的信息。通过DirectInput,游戏可以实时响应玩家的操作,实现更精确的控制。开发者可能在本章学习如何设置输入设备...
开发者可以利用C++的面向对象特性来设计可扩展的游戏架构,同时通过VC++的调试工具进行高效的问题排查。 接下来是DirectX,它是Microsoft为Windows平台开发游戏和多媒体应用程序提供的一个关键组件。DirectX包括多...
### Chapter 2 游戏编程介绍 * 工作内容 * 专业和细分 * 底层程序代码编写 * 模块程序设计 * 系统程序设计 重点:自主开发专用引擎 难点:模块程序设计 ### Chapter 3 基础理论 * 编程基本概念 * 编程思维 * ...
Game Theory Systems Chapter 20 Games as Systems of Conflict Chapter 21 Breaking the Rules Commissioned Game 2 Ironclad Unit 3 Play Chapter
《苹果电脑游戏设计》,英文名《mac game programming》,作者(美)Andre LaMothe,本书是为英文版,大小 34 MB。 由大师Andre LaMothe主编的游戏开发书籍.他写过一本叫做游戏开发大师技巧的书,好几千页,分上下卷,...