270
社区成员
发帖
与我相关
我的任务
分享
一、最终架构设计
迭代作业围绕 "冒险者 - 物品 - 关系" 的核心模型展开,最终架构采用面向对象设计,结合设计模式与数据结构实现复杂业务逻辑。主要包含以下模块:
核心实体类
Adventurer:冒险者类,维护属性(体力、攻击力、魔力等)、物品持有关系、雇佣关系及战斗 / 交互行为。Item及其子类:Bottle(药水瓶)、Equipment(装备,含Armour、Weapon等细分类)、Spell(法术),封装物品属性与使用逻辑。RelationManager:管理冒险者间的雇佣关系,维护上下级关系图。工具类与管理器
Factory:采用工厂模式,负责创建各类物品(药水瓶、装备等)。Observer相关接口:实现观察者模式,处理下级对上级的援助事件。InputParser:解析输入指令,分发至对应处理逻辑。核心逻辑模块
二、迭代中的架构调整及考虑
1. 深浅克隆与调试
Book和Bookshelf类实现深克隆方法,确保克隆对象独立(重写clone()或自定义复制构造函数)。Object ID验证克隆独立性,解决因浅克隆导致的属性联动修改问题。2. 容器与工厂模式
HashMap/HashSet管理物品,使用工厂模式创建对象。HashMap存储冒险者及其物品(以 ID 为键),优化查找效率。Factory类,通过静态方法根据类型创建Bottle、Equipment等,减少代码冗余。Equipment → Armour/Weapon → Sword/Magicbook,通过继承实现属性与行为差异化。HashMap)与去重需求(HashSet)。3. 观察者模式与雇佣关系
Observable(被观察者)与Observer接口:上级冒险者为被观察者,下级为观察者,血量变化时触发援助。RelationManager维护雇佣关系图,通过 DFS/BFS 判定上下级及盟友关系。fight和use指令逻辑,优先检查雇佣关系约束(如不可攻击上级)。4. 递归下降法(词法 / 语法分析)
Lexer类:拆分输入字符串为词法单元(如指令类型、参数),验证格式合法性。Parser类:基于文法规则(如<指令> → <类型> <参数列表>)递归解析词法单元,生成执行逻辑。三、架构设计原则与总结
Weapon继承Equipment)。HashMap:用于 ID 与对象的映射,支持 O (1) 查找。通过多轮迭代,架构从简单的类设计逐步演进为包含设计模式与复杂逻辑的系统,兼顾了可扩展性与业务复杂度。
使用Junit心得体会
在面向对象编程(OOP)的学习与实践中,JUnit作为Java生态下主流的单元测试框架,不仅是验证代码正确性的工具,更重塑了我对“设计-编码-测试”全流程的认知。从最初为满足作业覆盖率要求编写测试用例,到主动通过测试驱动代码设计,JUnit让我深刻体会到单元测试与面向对象思想的深度契合,也暴露了实践中容易陷入的误区。以下结合多次OOP作业经历,分享我的核心心得。
一、JUnit:让面向对象的“封装”与“复用”更可验证 面向对象的核心特性之一是封装,即将属性与行为封装在类内部,仅暴露必要接口。但封装也带来了“黑盒风险”——若无法通过接口验证内部逻辑正确性,类的可靠性将大打折扣。JUnit恰好为封装的类提供了“接口校验器”,让我能精准验证类的行为是否符合设计预期。 在“冒险者-物品”系统的作业中,我曾为`Adventurer`类设计了`buyItem()`方法,用于处理购买装备的逻辑(扣除金币、添加装备至背包)。最初仅通过主程序打印日志验证功能,不仅效率低,还容易遗漏边界场景(如金币不足、装备重复购买)。使用JUnit后,我针对该方法设计了3组核心测试用例: 1. 正常购买:传入足够金币与新装备,验证背包中是否新增装备、金币是否正确扣除; 2. 金币不足:传入低于装备价格的金币,验证背包无变化、金币保持不变; 3. 重复购买:购买已有的装备,验证背包不重复添加、金币不扣除。 通过`@Test`注解标记用例,结合`Assert.assertEquals()`等断言方法,每次修改`buyItem()`逻辑后,只需执行测试类即可快速定位问题。例如,我曾误将“装备ID唯一性校验”写在`Adventurer`类的`addItem()`方法中,导致`buyItem()`未触发校验,而JUnit的重复购买用例立即报错,帮我避免了这一隐藏bug。 此外,JUnit的“复用性”也与面向对象的复用思想高度契合。在后续迭代中,当`Adventurer`类新增“出售物品”“使用药水”等方法时,我可复用之前测试类中创建的`Adventurer`实例、`Item`实例,只需新增对应方法的测试用例,大幅减少了重复代码,这与OOP中“类的复用”理念不谋而合。
二、从“为测试而测试”到“测试驱动设计”:JUnit倒逼代码质量 最初接触JUnit时,我常陷入“为满足覆盖率要求而编写测试”的误区——机械地为每个方法编写用例,却忽略了测试与设计的关联。直到在“工厂模式创建物品”的作业中,我才意识到JUnit能反过来倒逼代码的面向对象设计更合理。 当时我需要实现`Factory`类,通过`createItem()`方法根据类型(如“HpBottle”“Sword”)创建不同物品。最初的代码直接在`createItem()`中用冗长的`if-else`判断类型,不仅扩展性差(新增物品需修改该方法),测试时也需为每个分支编写用例,代码臃肿且易出错。在编写JUnit测试用例时,我发现:若后续新增“ManaBottle”类型,不仅要修改`Factory`类,还需新增测试用例,违背了OOP的“开闭原则”(对扩展开放、对修改关闭)。 正是JUnit带来的“测试痛点”,让我重新优化设计:将`createItem()`拆分为`createBottle()`和`createEquipment()`两个方法,分别处理药水瓶和装备的创建,再在`createItem()`中调用对应方法。这样一来,新增物品类型只需扩展对应方法,无需修改原有逻辑;测试时也可针对`createBottle()`和`createEquipment()`分别编写用例,用例更聚焦,代码扩展性也显著提升。 这次经历让我明白:JUnit的价值不仅是“验证代码”,更是“提前暴露设计缺陷”。好的测试用例应与面向对象的设计原则(单一职责、开闭原则)相匹配,当测试用例编写困难、重复度高时,往往意味着代码设计存在问题——这正是“测试驱动设计(TDD)”的核心思想:先思考“如何测试”,再设计“如何实现”,让代码从源头就具备可测试性与可扩展性。
三、JUnit实践中的常见误区与解决方案:在多次作业中,我也踩过不少JUnit使用的“坑”,这些误区本质上是对“面向对象测试边界”的理解不足,总结如下:
1. 过度测试私有方法,破坏封装性 面向对象强调“封装私有实现”,但最初我为了追求100%覆盖率,试图通过反射测试`Adventurer`类的私有方法。这种做法不仅繁琐,还违背了封装原则——私有方法是类的内部实现,若后续修改其逻辑,所有依赖该方法的测试用例都需修改,增加了维护成本。 解决方案:聚焦“公有接口测试”,通过调用公有方法(如`buyItem()`)间接验证私有方法的逻辑。例如,测试`checkBudget()`无需直接调用它,只需通过“金币不足时购买失败”的用例,即可验证其正确性。
2. 测试用例依赖外部环境,导致结果不稳定 在“书架管理”作业中,我曾在测试`Bookshelf`类的`loadBooks()`方法时,直接读取本地文件中的书籍数据。但不同环境下文件路径可能不同,导致测试用例有时通过、有时失败,失去了单元测试的“稳定性”。 **解决方案**:使用“模拟对象”或“内存数据”隔离外部依赖。例如,通过`@Before`注解在每次测试前创建内存中的书籍列表,而非依赖外部文件;若需测试数据库交互,可使用Mockito等框架模拟DAO层对象,确保测试用例不依赖真实环境。
3. 忽视异常场景测试,导致边界bug 面向对象的方法往往存在“正常路径”与“异常路径”,最初我只关注正常场景(如“成功购买装备”“成功添加书籍”),却忽略了异常场景(如“传入null值”“参数越界”)。例如,在测试`StringUtil`类的`substring()`方法时,未测试“起始索引大于结束索引”的场景,导致后续实际使用时出现空指针异常。解决方案:使用`@Test(expected = 异常类.class)`或JUnit 5的`assertThrows()`方法专门测试异常场景。例如,测试“传入null的装备ID”时,应验证`buyItem()`是否抛出`IllegalArgumentException`,确保代码对异常输入的处理符合预期。
四、JUnit是面向对象编程的“最佳搭档” 回顾多次OOP作业,JUnit不仅帮我发现了数十个隐藏bug,更重要的是,它让我对面向对象的理解从“语法层面”深入到“设计层面”: -它让“封装”更可靠,通过接口测试验证类的行为,避免封装带来的黑盒风险,让“复用”更高效,测试代码的复用性与业务代码的复用性相互促进,让“设计”更合理——通过测试痛点倒逼代码遵循OOP原则,提升可扩展性。 未来的编程实践中,我将继续以JUnit为工具,坚持“测试先行”的思想:在编写业务代码前,先思考测试场景;在优化代码设计时,同步优化测试用例。一段能通过所有测试用例的面向对象代码,不仅是“正确的”,更是“可靠的”“可维护的”。
从面向过程到面向函数转变的心得体会
从面向过程到面向对象编程的转变,对我而言不是语法的简单切换,而是一场从 “流程驱动” 到 “模型驱动” 的思维重构。最初以为只是多了 “类” 和 “对象” 的概念,直到在实践中反复碰壁又逐渐通透,才理解其中的本质差异。
面向过程时,思考起点总是 “这件事要分几步做”。比如写图书管理系统,第一反应是拆解流程:“输入信息→存数组→遍历查询→定位修改”。数据(图书信息)和操作(增删查改函数)是分离的,像把书和书架拆成了零散零件。
转向面向对象后,我学会了先抽象现实中的实体。图书管理系统里,“图书” 和 “书架” 是独立存在的实体:Book类封装 名称等属性和getInfo()方法,Bookshelf类封装容量属性和add()/remove()行为,最后通过bookshelf.add(book)实现交互。代码与现实世界的映射更紧密了,数据有了 “类” 这个容器,函数成了容器的行为,世界从扁平变得立体。
面向过程中,我曾因全局变量吃过大亏。为让方法访问数据,把数组设为全局变量。后来新增方法时不小心修改了数组,导致计算错误,排查时全局变量的 “牵一发而动全身” 让我耗费数小时。
用面向对象重构时,数据成了类的私有属性,仅通过get函数暴露读取接口,修改需经set函数)管控。外部函数只能通过接口访问,无法直接篡改底层数据。这种封装不仅提升安全性,更带来了 “边界感”—— 修改数据的存储方式(比如从数组换链表),只要接口不变,外部依赖就无需改动。
面向过程的 “复用” 常是复制粘贴。写了Dog的bark(),再做Cat的meow()就得复制修改;新增通用行为又得重写,维护时稍不注意就漏改。
继承让复用变得优雅:Animal基类封装eat()、sleep(),Dog和Cat子类只需实现makeSound()。新增Bird时直接继承,无需重复基础行为。多态更让复用升维:用Animal数组存放各类动物,调用makeSound()时自动适配实际类型,新增Duck类只需实现接口,原有逻辑无需改动,完美契合 “开闭原则”。
从面向过程到面向对象,是从 “关注怎么做” 到 “关注是什么” 的视角跃迁。前者像写操作手册,后者像搭建微型世界 —— 先定义 “居民”(类),赋予属性和能力,再让他们按规则互动。这种转变,让编程从 “完成任务” 变成 “构建系统”,用更贴近人类认知的方式应对复杂问题,这或许是面向对象最珍贵的馈赠。
对OOPre课程的简单建议
可以对JAVA语法进一步细致讲解,可以适当放慢一点进度,使对内容学习更细致,许多内容靠自学有些难度