📢完整的题目要求和源代码请按下面指示前往我的Github仓库~

BUAA-2023-OOpre

题目信息

第二次作业指导书

在本次作业中,我们即将完成面向对象先导课程作业的基础代码编写,在以后的作业中,我们将在本次作业的基础上进行迭代开发。

第一部分:提交要求

请保证提交项目的顶层目录至少存在两个文件夹:srctest(命名需严格与此保持一致),请将作业的功能代码存放于src文件夹下,同时将相关junit测试类代码文件存放于test文件夹下,以保证评测的正常进行(评测时只会针对src目录下的文件进行程序功能的评测以及代码风格检测,也就是说,test目录下的junit测试代码风格不会被检测)。参考目录结构如下:

1
2
3
4
5
6
7
8
|-src
|- Bottle.java
|- Equipment.java
|- ...
|-test
|- BottleTest.java
|- EquipmentTest.java
|- ...

第二部分:题目描述

背景

在接下来的若干次作业中,同学们将进行以本次作业为基础的迭代开发,因此在具体的代码实现中,希望同学们可以考虑到每一次所写代码的可扩展性和可维护性,从而减少下一次的工作量。

在接下来的几次作业中,请想象你是一个穿越到魔法大陆上的冒险者,在旅途中,你需要收集各种道具,使用各种装备,招募其他冒险者加入队伍,提升自己的等级并体验各种战斗。

在本次作业中,你要做的是

实现冒险者类 Adventurer 、药水瓶类 Bottle 、装备类 Equipment


利用容器,管理所有冒险者,并管理每一个冒险者所拥有的药水瓶和装备

你可能需要实现的类和它们要拥有的属性有

Adventure :ID,名字,药水瓶和装备各自的容器


Bottle:ID,名字,容量(capacity)


Equipment:ID,名字,星级(star)

请注意,在作业中,可能会存在ID不同但名字相同的情况,请同学们在设计代码的时候考虑这一点

Bottle的容量属性在本次作业中不会被测试,但是却是后续作业的重要部分,请同学们不要忽略。

在本次作业中,初始时,你没有需要管理的冒险者,我们通过若干条操作指令来修改当前的状态

加入一个需要管理的冒险者(新加入的冒险者不携带任何药水瓶和装备)


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


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


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


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


给某个冒险者的某个装备提升一个星级(星级加1)

输入格式

第一行一个整数 n,表示操作的个数。

接下来的 n 行,每行一个形如 {type} {attribute} 的操作,{type}{attribute} 间、若干个 {attribute} 间使用若干个空格分割,操作输入形式及其含义如下。同时,为了方便测评,我们需要在需要执行一些指令后进行相关输出。具体要求也在下面的表中列出:

具体要求表
typeattribute意义输出格式(每条对应的占一行)
1{adv_id} {name}加入一个 ID 为 {adv_id}、名字为 {name} 的冒险者
2{adv_id} {bot_id} {name} {capacity}给 ID 为 {adv_id} 的冒险者增加一个药水瓶,药水瓶的 ID、名字、容量分别为 {bot_id}{name}{capacity}
3{adv_id} {bot_id}将 ID 为 {adv_id} 的冒险者的 id 为 {bot_id} 的药水瓶删除{一个整数} {一个字符串}(解释:整数为删除后冒险者药水瓶数目,字符串为删除的药水瓶的name)
4{adv_id} {equ_id} {name} {star}给 ID 为 {adv_id} 的冒险者增加一个装备,装备的 ID、名字、星级分别为 {equ_id}{name}{star}
5{adv_id} {equ_id}将 ID 为 {adv_id} 的冒险者的 id 为 {equ_id} 的装备删除{一个整数} {一个字符串}(解释:整数为删除后冒险者装备数目,字符串为删除的装备的name)
6{adv_id} {equ_id}将 ID 为 {adv_id} 的冒险者的 id 为 {equ_id} 的装备提升一个星级{一个字符串} {一个整数}(解释:字符串为装备的name,整数为装备升星后的星级)

输出数值时,你的输出数值需要和正确数值相等。

样例

1
2
3
4
5
4
1 700917 i$KdS=1n
4 700917 829431 ?TE/G1 3
6 700917 829431
5 700917 829431

期望输出

1
2
?TE/G1 4
0 ?TE/G1
1
2
3
4
3
1 700917 i$KdS=1n
2 700917 829431 ?TE/G1 3
3 700917 829431

期望输出

1
0 ?TE/G1

数据限制

变量约束
变量约束
变量类型说明
id整数取值范围:0 - 2147483647
name字符串保证不会出现空白字符,长度区间: (0,40)
capacity整数取值范围:0 - 2147483647
star整数取值范围:0 - 2147483647
操作约束
操作约束
  1. 保证所有的冒险者、药水瓶、装备 id 均不相同
  2. 保证删除了的药水瓶/装备的 id 不会再次出现
  3. 2-6保证所有冒险者均已存在
  4. 3/5/6保证该冒险者拥有操作中提到 id 的药水瓶/装备
  5. 保证增加的装备和药水瓶原本不存在
  6. 操作数满足1≤n≤2000

junit测试

我们在gitlab上准备了一份junit使用示例代码(基于hw1程序)以及一份junit使用文档供大家参考,推荐各位同学在课下测试时使用 junit 单元测试来对自己的程序进行测试

junit 是一个单元测试包,可以通过编写单元测试类和方法,来实现对类和方法实现正确性的快速检查和测试。还可以查看测试覆盖率以及具体覆盖范围(精确到语句级别),以帮助编程者全面无死角地进行程序功能测试。

此外,Junit 对主流 Java IDE(Idea、eclipse 等)均有较为完善的支持,具体的配置和使用方法可以参考gitlab上的使用文档。

要求

本次作业要求同学们需要自行编写junit测试代码对自己的代码进行测试。在本次作业中,检测到存在junit测试方法并可以成功编译即视为通过junit评测。

解析说明

关于第二次作业的解析与说明

第二次作业开始正式迭代开发,本次作业难度一般,主要目标是编写基础代码准备接下来几周的迭代与扩展

注意:划删除线的部分并非过时信息!

Part 1

指导书的大致要求是:
实现以下类

1
2
3
Adventure :ID,名字,药水瓶和装备各自的容器
Bottle:ID,名字,容量(capacity)
Equipment:ID,名字,星级(star)

以及实现以下操作

1
2
3
4
5
6
7
1.加入一个需要管理的冒险者(新加入的冒险者不携带任何药水瓶和装备)
2.给某个冒险者增加一个药水瓶
3.删除某个冒险者的某个药水瓶
4.给某个冒险者增加一个装备
5.删除某个冒险者的某个装备
6.给某个冒险者的某个装备提升一个星级
* 其中,提升星级的意思是,新星级=原有星级+1

并且做了一些约束使问题简化,这里不再赘述。

我认为本次作业主要考虑三点

如何用合适的容器对Adventure类、Bottle类以及Equipment类进行管理


上述三个类的内部结构如何实现


如何实现同时返回一个字符串和整数信息

对于第一点,我一开始的思路是,用ArrayList来管理三个类,这是一种很自然的想法。但是,在随后的具体实现中,我发现很多操作要求通过ID来获取对象,而由于ID属性被封装在各个类中,以我的水平只能ArrayList遍历来查找需要的冒险者、药瓶或者是装备。
这样带来的坏处是,需要自己实现遍历查找算法,而且遍历效率极低。同时checkstyle还会提示for循环部分重复度过高(查找BottleEquipment时有大量重复结构)。
那么,有没有更好的改进办法呢?我想到了下面的思路:

利用每个对象ID属性的唯一性,用ID作为Key,相应对象(Adventure类、Bottle类以及Equipment类)做为Value,构建HashMap来进行管理。

也就是如下三个容器:


1
2
3
HashMap<Integer, Adventure> adventures = new HashMap<>();
HashMap<Integer, Bottle> bottles = new HashMap<>();
HashMap<Integer, Equipment> equipments = new HashMap<>();

这样的好处是,HashMap类本身已经提供了通过Key快速查找Value的方法,而且代码简洁checkstyle也不会提示重复度过高的问题。可谓一举两得。

有了第一点的思路,各个类的内部结构也就比较清晰,以功能最多的Adventure类为例,应该如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//属性
private final int id;
private final String name;
private HashMap<Integer, Bottle> bottles = new HashMap<>();//管理Bottle
private HashMap<Integer, Equipment> equipments = new HashMap<>();//管理Equipment
//方法
public Adventure(int id, String name) {
//...
}
public void addBottle(Bottle bottle) {
//...
}

public void addEquipment(Equipment equipment) {
//...
}

public PrintInfo removeBottle(int bottleId) {
//...
}

public PrintInfo removeEquipment(int equipmentId) {
//...
}

public PrintInfo increaseStar(int equipmentId) {
//...
}

public HashMap<Integer, Bottle> getBottles() {
//...
}

public HashMap<Integer, Equipment> getEquipments() {
//...
}

java中,一个方法或者函数只能返回一个对象,那么如何同时返回一个字符串和整数呢?
一开始,我同样想用HashMap,但是又遇到问题:我们每次只需要一个Key-Value对,为此单独构造一个容器似乎没有必要,而且这也没有体现Key和Value的映射关系
因此经过考虑,我自定义了一个工具类PrintInfo,能根据一个整数值和字符串值构造对象,并提供不同顺序的打印方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class PrintInfo {
private final int numOrStar;
private final String name;

public PrintInfo(int numOrStar, String name) {
//...
}

public void printIntFirst() {
//...
}

public void printStringFirst() {
//...
}
}

这样 负责删除且需要返回结果的方法便可以声明返回该类型。

解决了这三个问题,Part 1的要求相信很快就能解决。

Part 2

本次作业Part 2要求不高,自行阅读文档,了解JUnit类的使用即可。