北航OOP结课感受

2024-11-02

面向对象导论课程结课感受

一、作业架构设计与调整

(一)架构设计

  1. 类的内容和作用
    • Adventurer类:代表冒险者,包含冒险者的基本属性如idnamehitPoint(生命值)、atk(攻击力)、def(防御力)等,以及各种物品的存储容器如bottleMap(瓶子集合)、equipmentMap(装备集合)等。它还包含了许多操作冒险者属性和物品的方法,如addAtk(增加攻击力)、addHitPoint(增加生命值)、addBottle(添加瓶子)、addEquipment(添加装备)等。
    • Equipment类:是所有装备的基类,包含idnamedurability(耐久度)、ce(战斗力)等属性,以及addDurability(增加耐久度)、subDurability(减少耐久度)等方法。
    • Sword类:继承自Equipment类,代表剑这种武器,重写了battle方法,用于计算剑在战斗中的伤害。
    • Axe类:同样继承自Equipment类,代表斧头,其battle方法有独特的计算伤害方式,会将被攻击者的生命值设为当前的十分之一。
    • Blade类:也是Equipment的子类,battle方法根据自身属性和攻击者攻击力计算对被攻击者造成的伤害。
    • Bottle类:是所有瓶子的基类,包含idnamecapacity(容量)、ce等属性,以及getIsEmpty(是否为空)、setIsEmpty(设置为空)等方法。
    • HpBottle类:继承自Bottle类,用于回复生命值,use方法实现了增加冒险者生命值并设置为空瓶的功能。
    • AtkBottle类:继承自Bottle类,使用后可增加冒险者攻击力,use方法中根据自身属性计算增加的攻击力。
    • DefBottle类:继承自Bottle类,能增加冒险者防御力,use方法按照规则增加防御力。
    • Fragment类:用于表示碎片,包含idname属性。
    • MainClass类:程序的入口类,包含main方法,用于读取指令数量和指令内容,并根据指令类型调用Operate类中的相应方法。
    • Operate类:包含一系列静态方法,用于执行各种操作,如添加冒险者、添加瓶子、添加装备、战斗等操作,这些方法会根据输入信息调用其他类的相应方法。
  2. 类与类的关系

ClassGraph

  • 程序入口

    • MainClassOperate之间存在“调用”关系。MainClass根据用户输入的指令来调用Operate类中的相应方法,以执行各种操作。

    • OperateAdventurer之间存在“调用”关系。Operate类中的方法会对Adventurer类的对象进行操作,例如添加冒险者、添加物品到冒险者的背包、执行冒险者的战斗操作等。

  • 冒险者类

    • 装备类
      • AdventurerEquipment之间存在“包含”关系。Adventurer类包含了Equipment类的对象,用于表示冒险者所拥有的装备。冒险者可以添加装备到自己的背包中(addEquipment方法),从背包中获取装备(getEquipment方法),并在战斗中使用装备(useEquipment方法)。
      • Equipment是装备的基类,SwordAxeBlade类都继承自Equipment。当冒险者使用相应类型的装备时,会调用对应的子类方法来实现特定的战斗逻辑。
    • 瓶子类
      • AdventurerBottle之间存在“包含”关系。Adventurer类包含了Bottle类的对象,用于表示冒险者所拥有的瓶子。冒险者可以添加瓶子到自己的背包中(addBottle方法),从背包中获取瓶子(getBottle方法),并在需要时使用瓶子(useBottle方法)。
      • Bottle是瓶子的基类,HpBottleAtkBottleDefBottle类都继承自Bottle。当冒险者使用相应类型的瓶子时,会调用对应的子类方法来实现特定的功能,如回复生命值、增加攻击力或增加防御力。
    • 碎片类
      • AdventurerFragment之间存在“包含”关系。Adventurer类包含了Fragment类的对象,用于存储碎片信息。冒险者可以获取碎片(getFragment方法),添加碎片到自己的背包中(addFragment方法),并使用碎片来执行一些特殊操作(useFragment方法)。

(二)架构调整

  • 在第三次迭代的时候,由于把执行操作的代码放在MainClass中,导致MainClass类超出行数限制,所有不得不重构读入指令的部分。将指令放在ArrayList中统一管理,在MainClass中定义了inputInfo这个ArrayList,用于存储输入的指令信息。在MainClass方法中读取指令数量后,将每一行指令按空格分割后存储到inputInfo中,然后根据指令类型在Operate类中进行相应的操作。

(三)bug修复

  1. 战斗身份搞反
    • 在进行战斗的时候,将被攻击者和攻击者的身份搞反了。
  2. 武器耐久度消耗错误
    • 每战斗一个人就消耗一点武器的耐久度,而实际上应该每一次战斗消耗一点武器的耐久。
  3. 删除逻辑错误
    • 删除分为两种,一种是永久删除,一种是从背包中删除,将二者混为一谈,并且对装备重名的逻辑处理错误,需要先判断物品在不在背包,然后才能根据物品的名字进行删除。
  4. 携带装备逻辑错误
    • 携带装备的时候,没有判断背包里面有没有重名装备,如果有,需要根据ID去删除,而不能根据名字删除,否则会与删除时的重名问题相同。
  5. 被雇佣者援助次数计算错误
    • 在被雇佣者援助雇佣者的时候,被雇佣者把自己的全部武器送给雇佣者后,才应该增加一次援助次数,而我写的是每给予一件装备增加一次雇佣次数。
  6. 战斗损失生命值计算错误
    • 战斗损失的生命值应当是战前生命值减去战后生命值,我将二者搞反,导致输出负数。
  7. 递归攻击容器未清空
    • 每次进行递归攻击的时候都需要清空每个被攻击者的递归雇佣容器,重新确定递归雇佣者,我在这一过程中没有清空容器,导致递归攻击对象不断增多。

二、JUnit使用心得体会

(一)初期使用

  • 在刚开始的几次迭代中,由于代码并不复杂,对于JUnit测试单元,能够按照逻辑去将每一种可能的情况都考虑到。这使得我能够较为顺利地编写测试用例,确保代码的基本功能正确。

(二)后期使用

  • 在后续的迭代中,情境越来越复杂,JUnit测试单元的书写难度越来越大,想要将每种情况、每个分支全部覆盖更是难上加难。为了满足课程组对于JUnit覆盖率的要求,我采取了偷懒的方法,使用已经通过的中测样例作为JUnit测试单元的内容。

(三)反思与教训

  • 这种偷懒的方法虽然能够快速满足覆盖率要求,但导致许多可能出bug的点没有得到充分的测试。最后两次迭代的强测都挂掉了,让我深刻认识到认真编写JUnit测试单元的重要性。以后我将引以为戒,真正把JUnit测试单元利用起来,考虑各种可能的情况,全面、充分地测试自己代码的正确性。

三、学习OOPre的心得体会

(一)从面向过程到面向对象的过渡

  • 从面向过程编程过渡到面向对象编程是一个具有挑战性、但也很有收获的过程。面向对象编程强调对象的概念,将数据和操作数据的方法封装在一起,使得代码更加模块化、易于维护和扩展。通过这次课程,我不仅了解了Java的基本语法,而且更深入地理解了类、对象、继承、多态等面向对象的核心概念,学会了如何运用这些概念来设计和实现一个较为复杂的程序架构。

(二)bug修复的感想与避免方法

  1. 认真审题明确需求

    • 在迭代作业编写代码过程中,我出现的很多bug都是因为没有认真审题,对需求理解不准确。例如在战斗中各种属性计算错误,就是没有清楚地理解题目中对于不同操作的要求,所以,以后在编写代码前,一定要认真仔细地阅读题目,确保对需求有清晰准确的理解。
  2. 逻辑问题仔细考虑
    • 许多bug是由于逻辑错误导致的,比如删除物品和携带装备时对逻辑的错误处理、援助次数计算错误等。在编写代码时,需要对每一个操作的逻辑进行仔细的思考和推敲,确保逻辑的正确性。可以通过绘制流程图、编写伪代码等方式来辅助自己梳理逻辑。
  3. 善用调试定位bug
    • 当出现bug时,调试是非常重要的手段。通过在关键代码处添加打印语句或者使用调试工具,可以帮助我们快速定位bug出现的位置和原因。在作业中,通过调试我发现了很多隐藏在复杂逻辑中的问题,并且能够进行修复。

四、对OOPre课程的建议

建议给全中测样例数据点,适当提高中测强度,进而降低debug的难度。这样在迭代过程中更好地发现问题,及时调整代码,而不是在强测挂了之后,面对海量的数据难以下手。