雪沫乳花浮午盏,人间有味是OO

肖杰方-24371471 2025-11-06 19:25:33

前言:轻轻的我走了,正如我轻轻的来,我敲一敲键盘,追忆OO的风采

一、作业架构设计

1、作业最终架构

img

​ 下面将一一介绍架构中的各个部分以及其背后的思考

(1)指令处理模块

​ 指令处理模块由三个类构成:

MainClass类(以下简称MC)。MC的唯一功能是读取指令并且根据空白符划分为多个部分,存储于二维数组中,然后将这个二维数组传给指令处理模块。

CommandProcessor类(单例模式,以下简称CP)。CP的作用便是解读各个代码,并向各个冒险者个体发放命令(为了方便,我选择让CP来管理所有冒险者)。我将指令划分为四类:

a)单体命令,即单独一个冒险者可以执行的命令;
b)use类指令,该指令存在使用者与被使用者;
C)fight类指令,该指令存在攻击发出者与被攻击者;
d)relation类指令,主要负责关系的构建与解除。

​ 需要强调的是,每一条指令都仅仅是一种通知,真正的指令执行者是冒险者本身。

Lexer类(以下简称LE)。因为关系构建存在文法解析的部分,我创建了它来辅助CPLE负责识别下一个语料的种类并将其整体返回给CPCP中的递归下降方法将根据文法规则构建不同冒险者之间的关系。


(2)冒险者模块

Adventurer类是本此任务中活动最频繁,也是最为重要的类,他是一切命令的实际执行者。我的冒险者拥由指令执行区状态调整区所属物管理区

​ 指令执行区:Adventurer的执行区同样被我划分为四个部分,与上文中的CP指令类型一一对应

a)单元命令执行区
b)use命令执行区
c)fight命令执行区
d)relation命令执行区

​ 在执行指令的前后,冒险者还会检测指令的成功性。如果出现了违规操作(如攻击BOSS),那么冒险者会输出相应警告信息;如果操作成功,冒险者则会大肆炫耀,根据输出规则将自己做的事公之于众。

​ 状态调整区:冒险者在某一动作(usefightrelation)执行时会做出相应的反应(主动会被迫),如相关参数的改变,求助信号的发出等等。与此同时,该区域也负责输出冒险者变化后的状态。

​ 所属物管理区:管理物品与关系。冒险者拥有三种“物品”——药水、装备与法术,以及两种“关系”——老板与手下,他们的管理都被放在这一个区域。有关“物品”的功能有加入背包、移除背包、随身携带等等;有关“关系”的功能有寻找下级、判断是否为同盟,判断是否为上级等等。


(3)物品模块

​ 该模块由药水、装备与法术构成(我将这三种类型成为大类)。三者并无共同父类,因为在我看来三者的功能有很大不同,放在同一个父级之下并不能简化我的工作。三者各自衍生,诞生了许多子类(将其成为小类),同样有着不同的功能。

​ 三者的产生都来源于工厂(抽象工厂模式),冒险者在调用工厂时需要知道物品的大类,工厂内部负责判断小类并生成相应对象返回给冒险者。

​ 药水和法术都可以当作被use的道具,因此我引入了接口usable,该接口内部方法useTo有最终执行use指令的能力,对use的被使用者造成实质性地影响,并返回布尔值给发出者,以反馈执行结果。

2、迭代中的架构调整和考虑

(1)有关容器的使用

Java中的Arraylist作为非常智能的容器,一定程度上实现了队列的功能,这使得它天生拥有“后进后出”的时序性(即后加入的元素一定拥有最大的标),同时它能释放被删除元素的内存,极大的节省了空间。于是我一开始便将所有容器都定义为ArrayList类型。但是后来学习了有关HashMap的知识,并决定将CP中管理冒险者的容器改为HashMap类型,这样可以减少很多for循环找人的过程,提高效率的同时降低了代码量。类似的,我也将冒险者的药水、装备、法术背包更改为此类型。我并未更改“被携带的药水”的容器类型,主要是考虑到上文中提到的时序性。


(2)有关工厂模式

​ 因为没有接触过工厂模式的思想,我起初选择让冒险者自身来创建新的对象,在这个过程中,冒险者需要自己判断对象的具体类型(精确到小类),然后进行类型转换放入容器中,这极大程度上增添了代码的复杂性。于是在学习工厂模式之后,我果断采用了抽象工厂,创建了Factory类,冒险者只需要将信息传递给工厂,工厂会自动判断类型然后以“大类”
的方式将指针返回给冒险者。


(3)有关单例模式

​ 类似于工厂模式,单例模式的学习比较靠后,所以我一开始并未采用。后来随着命令种类的不断增加,main不再能容许更多的代码来进行种类识别于任务发放,于是单例的CP应运而生。对于单例模式,我认为最重要的是保证其存在的单一性,不然很容易产生错误解读指令的现象。


(4)有关继承

​ 根据题目描述,药水与装备都应该是item类的子类,因为他们拥有共同的性质——有ID、可以被加入背包(其实也就这些了)。很不幸的是,越往后期迭代,他们的共性越来越小,分歧越来越大,管理方式也不尽相同,此时我认为再让他们同时继承item类就失去了意义,既不能方便冒险者对他们的管理,也不能减少代码量,于是我删除了item,让他们各自为安。

二、使用JUnit的心得体会

​ 直观上很不好用!!!但是确实很重要(悲)

​ 因为覆盖率检测的问题,我选择从CP转到测试代码,这样的话可以通过给予cp指令来测试整个项目,不仅方便编写测试代码,还符合强侧规则,一举两得。

​ 相比于使用assert来判断代码正确性,我更倾向于对比标准输出,这样更加直观,但是这样的行为是有代价的,我的代码可能会产生一些意料不到的错误,所以我会选择在所有指令的最后针对每一个冒险者的状态进行assert判断,保证每一个数值都正确。

JUnit使用最为重要的是测试数据的生成,很多时候某些极端的数据更能体现出程序内部的错误,下例就是我通过JUnit测试出来的Bug

condition:某个冒险者的`mana`值跟某个法术的消耗值相等时,该法术应当使用成功
output:使用失败
reason:判断时使用>而非>=

​ 总而言之,JUnit最大的挑战在于构造巧妙地数据以测试自己代码的完备性,在未来我们搭建评测机时,除了用随机生成的方式产生数据,还应当针对代码内容生成极端数据或者陷阱数据,而现在JUnit的使用就是在为这一部分奠定基础。

三、学习OOPre的心得体会

​ 据我所知,OOPre课程仅仅是一个小小的过渡,与其说是在学习OOPre,不如说我们在学习Java。这段时间的学习更像是在学习古诗鉴赏前的认字环节。虽然没有OO正课那样令人神往,但也是打好基础的关键步骤。在课程中我熟悉了gitlab的使用,学会了常见的Java语法,并且能用面向对象的方式实现微型项目的运行,但是更为重要的事,我用亲身的经历体验到了面向对象与面向过程两种编程方式的区别。

​ 相比于面向过程中的强调步骤和顺序,面向对象更加强调于行为和封装,每个类都各司其职,“高内聚低耦合”的实现方式让我们不再纠结于不同类不同方法之间的爱恨情仇,只要我们能够使他们的层次分明,避免循环调用,那么程序运行的逻辑将会无比清晰,在思考问题或者处理bug时能够更加得心应手。面向对象中的继承更加让我眼前一亮,这种规格化的管理方式极大程度上降低了代码重复率,使程序美观的同时也让思考的脉络更加清晰,同时父类指针对子类的调用更是让成员管理变得简单,子类对父类方法的重写让成员功能变得多元化。

​ 总而言之,OOpre的学习算是为我的OO生涯开了个好头,在学习到不少知识的同时让我体验到面向对象编程的愉快经历(总感觉自己在维护一件艺术品),希望未来我能用OO谱写更加辉煌的篇章。

四、对OOPre课程的简单建议

1、给出JUnit测试代码的模板,比如如何导入连续的输入,而不是将指令一行行地加入

2、在计算覆盖率的时候去除new方法

...全文
40 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

270

社区成员

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

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