六次迭代:菜鸟的 OOP 架构是如何演进的

高晨凯-24373278 2025-11-16 00:16:08

1 架构设计与演进

1.1 作业的最终架构

img

1.2 迭代中的架构调整

1.2.1 作业前期(hw1-3)

在这个阶段中,相关逻辑以及不同类之间的调用还较为简单。只需要抽象出 Spell、Bottle 等父类进行管理,具体行为在子类中执行,最后的架构就会比较清晰。
此外,随着指令增多,将对指令的分解过程分离到单独的类 CommandHandler 里。最顶层的 Main 类只暴露出指令字符串的传入,然后由 CommandHandler 传入 AdventurerManager 类里进行具体操作
值得注意的是,在 hw3 中我们需要增加的一个最重要的要求就是在执行具体的指令前,必须检查相关的冒险者的存活情况。如果在每个操作前都用 if (a.getAlive) 来判断的话,势必出现大量的重复代码,也不利于日后修改。因此我们在 Adventurer 类里增加 executeIfAlive() 方法要求所有操作必须在冒险者存活的情况下进行,将运行逻辑和判断逻辑进行分离,具体代码如下:

public boolean executeIfAlive(Runnable action) {
    if (this.death) {
        System.out.println(this.id + " is dead!");
        return false;
    }
    action.run();
    return true;
}

1.2.2 作业中期(hw5)

这个阶段增加了最核心的战斗系统以及其配套的金钱系统,并细化了背包系统、增加了工厂模式。
由于战斗系统常常涉及多个冒险者而且逻辑较为复杂,简单粗暴地将所有的执行都交由 AdventurerManager 类处理显然是困难的。因此我们分离出 BattleSolver 类来完成战斗的具体行为。
而随着对所携带物品要求的增加,直接用多个 HashMap 在 Adventurer 类里管理显然明显降低了代码的可读性和可维护性。因此我们增加 Backpack 类对所携带物品和其增加和去除的逻辑进行统一管理,并在 Adventurer 类中实例化 Backpack 类使得每一个冒险者都拥有这样一个符合要求的背包。
此外,随着指令数量的进一步增加,为减少单一方法的复杂性和长度,在 CommandHandler 类中对每一个指令都增加一个单独的 execute 方法将数据分发到不同的类和方法中进行操作。而原来的方法只判断指令的类型并跳转到对应的 execute 方法中。

public void execute(String line) {
    String[] parts = line.split("\\s+");
    String command = parts[0];

    switch (command) {
        case "aa":
            executeAddAdventurer(parts);
            break;

        // ... other cases ...

        default: 
            System.err.println(command + " not recognized");
            break;
    }
}

private void executeAddAdventurer(String[] parts) {
    String advId = parts[1];
    manager.addAdventurer(advId);
}

// ... other executes ...

1.2.3 作业后期(hw6-7)

在这个阶段,最后补充了雇佣系统,以及在其之上的援助功能和要求操作前基于雇佣关系进行判断。
同上所述,我们势必要增加 RelationManager 和 HelpObserver 类对相关的数据和逻辑进行管理和操作。在这个时候,我们发现在工程中,有超过一处以上的地方需要调用如 RelationManager 和 AdventurerManager 类的方法和数据,因为这个类里存储数据,我们无法在每次需要调用时重新实例化。而在不同的方法中进行传递又过于复杂和臃肿,因此在这阶段引入了单例模式来提供全局的访问。

// 在被访问类中
private static Class instance = new Class();

public static Class getInstance () {
    return instance;
}

// 在需访问的类中
private static class = Class.getInstance();

class.method();

此外,我们发现对于存活情况和雇佣关系的检查位于所有操作之前,因此我们将这两个检查统一提前到 CommandHandler 里进行,便于后续的维护。
同时,随着所需类的增加,为了方便在开发中更容易找到我们需要的具体某个类,因此我们将相关较强的类在一个包内进行存储,具体分包如下:

src
├── core
└── model
     ├── bottle
     ├── equipment
     └── spell

2 使用 JUnit 的心得体会

JUnit 对我来说最大的帮助是明确系统的预期行为。尤其是在处理战斗、死亡事件、关系更新等复杂交互时:

  • 通过 @Before 方法构造测试环境,让每个测试互不影响
  • 对 RelationManager 这样的单例,学会在测试前进行清空
  • 一些边界情况(死者、无法施法者、循环关系等)也能通过测试提前发现 Bug

通过 JUnit 测试,我们不再只能根据输出结果来判断运行的情况。此外,在过去我们常在过程中插入输出来判断当前的状态,而这种方法仍需要自己进行肉眼上的对照,在测试结束后还需要一个个注释掉或删除。而现在我们可以用更完善和独立的 JUnit 测试保证复杂逻辑的安全性,也能敦促我们在开发过程中尽量注意到不同的分支和细节

3 学习 OOPre 的心得体会

3.1 从面向过程到面向对象的转变

在 OOPre 前,一般我们写的基本都是流程式代码。课程的迭代设计让我开始将认知从“如何写功能”转向“如何划分对象的责任和关系”。此外,在对类的分离和提取抽象父类的过程中,我理解了在 Java 中对类的划分不再是为了区分流程而将代码分段,而是为了对应和表达实际中的角色和其行为

3.2 模块化与抽象能力的提升

在这次作业里,我开始对“怎么写架构”有了一定的认知和理解。尝试将单例、观察者模式等利用(或者说套用)在自己的代码里,能明显感觉到程序变得更清爽、更好维护。尤其是把类似解析和执行逻辑这种不同的功能进行分离之后,整个工程的结构显得更加清晰和通顺,不容易写到一半就忘记和原有代码的关联和接续关系。所以通过这次的课程能让我们认识到,写代码不只是把功能堆上去,合理地拆分和封装才是真的在“搭建”一个系统。

4 对 OOPre 课程的简单建议

  1. 可以增加一次“重构向”的作业,统一回顾前面所有内容。让大家更明确的理解和感受架构演进的过程,而不只是不断堆功能。
  2. 希望适当增加中测的强度,至少能够判断所有的新操作和方法是否正常运行。
...全文
72 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

271

社区成员

发帖
与我相关
我的任务
社区描述
2026年北航面向对象设计与构造
java 高校
社区管理员
  • 孙琦航
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧