面向对象导论课程结课感受
一、作业架构设计与调整
(一)架构设计
- 类的内容和作用
- Adventurer类:代表冒险者,包含冒险者的基本属性如
id
、name
、hitPoint
(生命值)、atk
(攻击力)、def
(防御力)等,以及各种物品的存储容器如bottleMap
(瓶子集合)、equipmentMap
(装备集合)等。它还包含了许多操作冒险者属性和物品的方法,如addAtk
(增加攻击力)、addHitPoint
(增加生命值)、addBottle
(添加瓶子)、addEquipment
(添加装备)等。 - Equipment类:是所有装备的基类,包含
id
、name
、durability
(耐久度)、ce
(战斗力)等属性,以及addDurability
(增加耐久度)、subDurability
(减少耐久度)等方法。 - Sword类:继承自
Equipment
类,代表剑这种武器,重写了battle
方法,用于计算剑在战斗中的伤害。 - Axe类:同样继承自
Equipment
类,代表斧头,其battle
方法有独特的计算伤害方式,会将被攻击者的生命值设为当前的十分之一。 - Blade类:也是
Equipment
的子类,battle
方法根据自身属性和攻击者攻击力计算对被攻击者造成的伤害。 - Bottle类:是所有瓶子的基类,包含
id
、name
、capacity
(容量)、ce
等属性,以及getIsEmpty
(是否为空)、setIsEmpty
(设置为空)等方法。 - HpBottle类:继承自
Bottle
类,用于回复生命值,use
方法实现了增加冒险者生命值并设置为空瓶的功能。 - AtkBottle类:继承自
Bottle
类,使用后可增加冒险者攻击力,use
方法中根据自身属性计算增加的攻击力。 - DefBottle类:继承自
Bottle
类,能增加冒险者防御力,use
方法按照规则增加防御力。 - Fragment类:用于表示碎片,包含
id
和name
属性。 - MainClass类:程序的入口类,包含
main
方法,用于读取指令数量和指令内容,并根据指令类型调用Operate
类中的相应方法。 - Operate类:包含一系列静态方法,用于执行各种操作,如添加冒险者、添加瓶子、添加装备、战斗等操作,这些方法会根据输入信息调用其他类的相应方法。
- Adventurer类:代表冒险者,包含冒险者的基本属性如
- 类与类的关系
-
程序入口
-
MainClass
与Operate
之间存在“调用”关系。MainClass
根据用户输入的指令来调用Operate
类中的相应方法,以执行各种操作。 -
Operate
与Adventurer
之间存在“调用”关系。Operate
类中的方法会对Adventurer
类的对象进行操作,例如添加冒险者、添加物品到冒险者的背包、执行冒险者的战斗操作等。
-
-
冒险者类
- 装备类
Adventurer
与Equipment
之间存在“包含”关系。Adventurer
类包含了Equipment
类的对象,用于表示冒险者所拥有的装备。冒险者可以添加装备到自己的背包中(addEquipment
方法),从背包中获取装备(getEquipment
方法),并在战斗中使用装备(useEquipment
方法)。Equipment
是装备的基类,Sword
、Axe
和Blade
类都继承自Equipment
。当冒险者使用相应类型的装备时,会调用对应的子类方法来实现特定的战斗逻辑。
- 瓶子类
Adventurer
与Bottle
之间存在“包含”关系。Adventurer
类包含了Bottle
类的对象,用于表示冒险者所拥有的瓶子。冒险者可以添加瓶子到自己的背包中(addBottle
方法),从背包中获取瓶子(getBottle
方法),并在需要时使用瓶子(useBottle
方法)。Bottle
是瓶子的基类,HpBottle
、AtkBottle
和DefBottle
类都继承自Bottle
。当冒险者使用相应类型的瓶子时,会调用对应的子类方法来实现特定的功能,如回复生命值、增加攻击力或增加防御力。
- 碎片类
Adventurer
与Fragment
之间存在“包含”关系。Adventurer
类包含了Fragment
类的对象,用于存储碎片信息。冒险者可以获取碎片(getFragment
方法),添加碎片到自己的背包中(addFragment
方法),并使用碎片来执行一些特殊操作(useFragment
方法)。
- 装备类
(二)架构调整
- 在第三次迭代的时候,由于把执行操作的代码放在MainClass中,导致
MainClass
类超出行数限制,所有不得不重构读入指令的部分。将指令放在ArrayList
中统一管理,在MainClass
中定义了inputInfo
这个ArrayList
,用于存储输入的指令信息。在MainClass
方法中读取指令数量后,将每一行指令按空格分割后存储到inputInfo
中,然后根据指令类型在Operate
类中进行相应的操作。
(三)bug修复
- 战斗身份搞反
- 在进行战斗的时候,将被攻击者和攻击者的身份搞反了。
- 武器耐久度消耗错误
- 每战斗一个人就消耗一点武器的耐久度,而实际上应该每一次战斗消耗一点武器的耐久。
- 删除逻辑错误
- 删除分为两种,一种是永久删除,一种是从背包中删除,将二者混为一谈,并且对装备重名的逻辑处理错误,需要先判断物品在不在背包,然后才能根据物品的名字进行删除。
- 携带装备逻辑错误
- 携带装备的时候,没有判断背包里面有没有重名装备,如果有,需要根据
ID
去删除,而不能根据名字删除,否则会与删除时的重名问题相同。
- 携带装备的时候,没有判断背包里面有没有重名装备,如果有,需要根据
- 被雇佣者援助次数计算错误
- 在被雇佣者援助雇佣者的时候,被雇佣者把自己的全部武器送给雇佣者后,才应该增加一次援助次数,而我写的是每给予一件装备增加一次雇佣次数。
- 战斗损失生命值计算错误
- 战斗损失的生命值应当是战前生命值减去战后生命值,我将二者搞反,导致输出负数。
- 递归攻击容器未清空
- 每次进行递归攻击的时候都需要清空每个被攻击者的递归雇佣容器,重新确定递归雇佣者,我在这一过程中没有清空容器,导致递归攻击对象不断增多。
二、JUnit使用心得体会
(一)初期使用
- 在刚开始的几次迭代中,由于代码并不复杂,对于JUnit测试单元,能够按照逻辑去将每一种可能的情况都考虑到。这使得我能够较为顺利地编写测试用例,确保代码的基本功能正确。
(二)后期使用
- 在后续的迭代中,情境越来越复杂,JUnit测试单元的书写难度越来越大,想要将每种情况、每个分支全部覆盖更是难上加难。为了满足课程组对于JUnit覆盖率的要求,我采取了偷懒的方法,使用已经通过的中测样例作为JUnit测试单元的内容。
(三)反思与教训
- 这种偷懒的方法虽然能够快速满足覆盖率要求,但导致许多可能出bug的点没有得到充分的测试。最后两次迭代的强测都挂掉了,让我深刻认识到认真编写JUnit测试单元的重要性。以后我将引以为戒,真正把JUnit测试单元利用起来,考虑各种可能的情况,全面、充分地测试自己代码的正确性。
三、学习OOPre的心得体会
(一)从面向过程到面向对象的过渡
- 从面向过程编程过渡到面向对象编程是一个具有挑战性、但也很有收获的过程。面向对象编程强调对象的概念,将数据和操作数据的方法封装在一起,使得代码更加模块化、易于维护和扩展。通过这次课程,我不仅了解了Java的基本语法,而且更深入地理解了类、对象、继承、多态等面向对象的核心概念,学会了如何运用这些概念来设计和实现一个较为复杂的程序架构。
(二)bug修复的感想与避免方法
-
认真审题明确需求
- 在迭代作业编写代码过程中,我出现的很多bug都是因为没有认真审题,对需求理解不准确。例如在战斗中各种属性计算错误,就是没有清楚地理解题目中对于不同操作的要求,所以,以后在编写代码前,一定要认真仔细地阅读题目,确保对需求有清晰准确的理解。
- 逻辑问题仔细考虑
- 许多bug是由于逻辑错误导致的,比如删除物品和携带装备时对逻辑的错误处理、援助次数计算错误等。在编写代码时,需要对每一个操作的逻辑进行仔细的思考和推敲,确保逻辑的正确性。可以通过绘制流程图、编写伪代码等方式来辅助自己梳理逻辑。
- 善用调试定位bug
- 当出现bug时,调试是非常重要的手段。通过在关键代码处添加打印语句或者使用调试工具,可以帮助我们快速定位bug出现的位置和原因。在作业中,通过调试我发现了很多隐藏在复杂逻辑中的问题,并且能够进行修复。
四、对OOPre课程的建议
建议给全中测样例数据点,适当提高中测强度,进而降低debug的难度。这样在迭代过程中更好地发现问题,及时调整代码,而不是在强测挂了之后,面对海量的数据难以下手。