0%

[OO Pre]2025 面向对象程序设计先导课程总结

最终架构设计

最终架构
(IDEA 自动生成)

架构设计描述

  • Main: 程序的入口。受到 C 语言编程习惯(?)的影响,将解析输入逻辑、全体冒险者容器放在了这里。随即调用一个静态方法执行接下来的逻辑。

  • Factory: 这是整个项目设计最失败的一个部分。本意是根据输入为两种 Spell 和四种 Bottle 生成对应的实例,但进入迭代的中后期,我把一些具有特定功能,但不属于工厂模式思想的静态方法也放到了这个类中,造成这个名为 Factory 的类实质上成为了一个定义了很多“函数”的“头文件”,其中就包括 Main 调用的执行整个游戏逻辑的 execute 方法。这个问题应当在后续的课程作业中避免。

  • Master, Servant 接口: 学习了观察者模式,在 Adventurer 类里实现了上级呼救、下级尝试治疗上级等方法。

    • Adventurer: 冒险者类。管理已有药水瓶、已携带药水瓶、已装备的武器/防具等,并实现相关方法。
  • Usable 接口: SpellBottle 同属“可用的物品”,故提供“使用物品”“检查可用性”等几个接口供其实现。

    • Spell: 按功能实现 AttackSpellHealSpell 两个子类。管理法术效果、蓝耗、id 等并实现相关方法。
    • Bottle: 按功能实现 HpBottle, AtkBottle, ManaBottle, DefBottle 四个子类。管理药水瓶效果,id 等并实现相关方法。
  • Equipment: 管理装备 CE 值,id 等并实现相关方法。

    • Weapon
      • Sword
      • Magicbook
    • Armour
  • LexerParser: 词法分析和语法分析。Lexer 将分析结果传递给 Parser,后者用递归下降法添加雇佣关系。

架构的调整与思考

迭代思路

第一次迭代

实现冒险者类 Adventurer 、药水瓶类 Bottle 、装备类 Equipment。为冒险者构建容器管理拥有的 BottleEquipment。实现:

  • 增加冒险者;

  • 给某个冒险者增加一个药水瓶;

  • 给某个冒险者增加一个装备;

  • 删除某个冒险者的某个药水瓶;

  • 删除某个冒险者的某个装备。

并给出对应输出。

第二次迭代

实现并细化法术类 Spell 等,细化药水瓶类 Bottle,添加 Usable 接口,实现:

  • 给某个冒险者学习一个法术;

  • 冒险者尝试携带他拥有的某个物品;

  • 冒险者对一个目标使用某个可用物品;

并给出对应输出。

第三次迭代

为冒险者类增加已装备的药水瓶,已装备的武器,已装备的防具属性,控制其数目。构建战斗机制,金钱系统,实现:

  • 冒险者去商店进行一次购买操作;

  • 冒险者进行一次战斗;

并给出对应输出。

第四次迭代

为冒险者类增加所有上级、所有下级属性,添加 Master Servant 接口,构建雇佣关系添加、援助上级机制,实现:

  • 增加一条雇佣关系;

  • 删除一条雇佣关系;

并给出对应输出。

第五次迭代

利用词法和语法分析(还不是很熟练)解析 load relation 指令,实现:

  • 导入一组雇佣关系;

并给出对应输出。

工厂模式与接口的运用

  • 在迭代到为冒险者添加使用物品的方法时,由于 Bottle 类和 Spell 类细化,因此学习了工厂模式,建立了 Factory 类生成对应的物品。

  • 学着提炼了可用物品的共同点,建立了 Usable 接口,并让 Bottle 类和 Spell 类实现。

  • Adventurer 设计 MasterServant 接口,应用观察者模式来实现雇佣关系功能。

数据结构的调整

开发早期采用给每一个 Bottle 提供一个 isEquipped 属性来区分已装备和未装备的药水瓶。后来添加了 10 个携带药水瓶的限制,并且要求溢出时移除最早加入的,于是换用了 ArrayDeque 双端队列这个数据结构保存已携带的药水瓶,非常方便地实现了上述功能。
其余的主要使用 ArrayList

未选用递归

关于雇佣和上下级关系:由于援助需要得知每个人的直接和间接上级、下级。本来计划给 Adventurer 一个唯一的直接上级 master 和一个列表的直接下级 servants。通过递归的方式找出所有盟友。然而我最终选择给每个 Adventurer 两个数组 mastersservants 用来保存所有上级和下级(直接以及间接),添加关系时将直接和间接关系全部归位,方便援助时查询。

JUnit 使用心得

JUnit 非常好用!

JUnit 的主旨是自动化测试,需要摒弃人来判断结果正确与否的过程。我花了一些时间才领悟到这一点,在此之前仍然是键盘输入观察输出的方式 debug。但有了 JUnit,测试各种极端情况变得很容易,不再需要键盘输入一堆前置命令做铺垫,而是可以用 Java 的方法构建出待测的环境,让计算机评判输出是否符合预期,而不是瞪眼法。

不过由于架构问题,我还是通过模拟键盘输入的方式测试了一部分代码,我觉得这应该不太正常,有待改正。

覆盖率测试结果也很具参考意义。编写单元测试时需要尽量测到所有的可能性,以免在代码中遗留 bug。

另外,JUnit 覆盖率测试中的方法覆盖率测试包括了各个类的构造方法,即便隐式声明的也包含在其中。

学习 OOPre 的心得体会

在 OOPre 之前我没有接触过 Java,面向对象编程思想也是浅尝辄止。整个 OOPre 下来我花费了大约 31 小时写代码作业(其中大约 6 小时写单元测试),项目代码行数在 1400 行左右,可以算是初步体会了面向对象程序设计的过程和一些方法,了解了如何运用单元测试 debug。由于熟练度问题,我的编写过程仍然受到典型的面向过程思维影响,但是正是这种对程序设计思维方式进行转变的尝试让我能够更深入地理解二者之间的差异。

Java 相比 C 更加便捷,提供了很多现成的数据结构,这让应对迭代作业中不同的要求更加得心应手,接近的语法让上手变得很快,并且很多封装好的方法节约了大量的重复逻辑编写。此外 Java 这样纯粹的面向对象语言更适合我学习如何提炼不同事物的共性,构造不同的类实现功能,学习继承、封装、多态的方法和思想,实现尽可能的代码和逻辑复用。这样分门别类让整体架构非常清晰,确实适合开发大型的项目。

在 OOPre 中我感到最有收获的时刻是学习并应用设计模式的时候。当我为不知道 Bottle 类细分的若干子类如何方便地实例化而头疼的时候,我接触了工厂模式。在那之前我对类还停留在“都需要实例化才能发挥作用”的阶段,全然没有想过构造一个只提供静态方法,而不提供实际个体的类,这的确打开了我的思路(导致我把所有静态方法都放在这里了)。而学习了观察者模式,让我对如何构建一对多的关系有了更好的理解。

对 OOPre 课程的建议

可以增设一些对 Java 语法、常用包、数据结构的介绍,方便在编写过程中查询和使用。

-------------本文结束 感谢您的时间-------------

欢迎关注我的其它发布渠道