第二次作业就算开始面向对象的入门啦
我也不会写什么很硬核的东西hh
如果你不幸点进来了随便看看就好了

作业要求回顾

  • 学会构建构造方法
  • 理解封装的作用
  • 学习简单容器的使用
  • 学习git的基础知识

有关git的内容我会单开一个文章集合,这里就不涉及啦~

题目背景

我是一个穿越到魔法大陆上的冒险者,在旅途中我们需要收集各种道具,使用各种装备,招募其他冒险者加入队伍,提升自己的攻击力和防御力并体验各种战斗。

实践

构建构造

  • 基本数据类型: 类似C语言
  • 复合数据类型:使用new
    1
    Bottle bottle = new Bottle(id,value);

简单容器

ArraylList是类似于C语言中数组的一个存在,但更加强大,用起来也更加方便。考虑到后面要用到相关知识,所以我们把这个内容提前到前面来。

ArrayList类位于 java.util 包中,使用前需要引入

1
import java.util.ArrayList;

接下来,我们以Bottle类(有两个基本属性,botIdvalue)为例,来讲解ArrayList的一些基本用法。

  1. 创建容器
    1
    ArrayList<Bottle> bottles = new ArrayList<>();
  2. 加入元素
    1
    Bottle bot1 = new Bottle("HealPotion", 50);
  3. 删除元素
    1
    2
    bottles.remove(bot1);//从容其中删除bot1
    bottles.remove(i);//删除容器中下标为i元素
  4. 判断元素是否在容器中
    1
    boolen in = bottles.contains(bottle);
  5. 访问下标为i的元素
    1
    Bottle bottle = bottles.get(i);
  6. 容器的大小
    1
    int size = bottles.size();
  7. 遍历容器中的元素
    1
    2
    3
    for (Bottle item : bottles){
    //do something
    }

对类进行封装

在本题中,我们需要定义的类有

  • Adventure (ID)
  • Bottle (ID,effect)
  • Equipment (ID)

当然,这只是我们在阅读题目后对这三个类的一个初步构想。考虑到每个冒险者都有自己的装备库药水库,所以我们给Adventure增加两个属性(BotListEquipList)。

在确定属性之后,我们要考虑的就是构建什么样的方法。
在本题中,我们涉及到以下操作:

  • 添加Adventure
  • Adventure添加/删除bottle

    删除后要输出Adventure剩余的bottle数量,以及删除的bottlevalue

  • Adventure添加/删除equipment

    删除后要输出Adventure剩余的equipment数量

这时候我们会有一个自然的想法(也可能只有我有好吧),
我们添加/删除bottle的方法能否放在bottle类里呢?
添加/删除bottle的操作显然和bottle有关,
并且类似的添加/删除操作还有equipment,如果这些方法都放在Adventure类里是不是太挤了呢?
而放在各自的类里显然会更加简洁清晰。

但是,这是不能的。(也可能可以好吧,但作者目前知识有限,多多包容~)
因为添加/删除这些操作的对象表面上是bottle,
但实际上是包含bottle的容器,也就是bottleArrayList
bottle类里的操作对象显然是元素,而不是容器。
而只有在Adventure类里可以对Adventure的属性bottleArrayList(也就是一个容器)进行操作。

那么理清楚这个关系之后我们就可以设计好这三个类以及Main类里需要什么方法了。

  • Main(注意这里的方法定义要写static)

    • 添加Adventure

      接口设置为(Adventure)
      接口还要有AdventureList,因为方法在运行函数外面,而在运行函数里定义的量相当于C语言里的局部变量。
      这样函数里只有一行add操作,实在是太easy啦!

  • Adventure

    • 添加/删除bottle 接口设置为(bottle)
    • 添加/删除Equipment接口设置为(Equipment)
  • Bottle

    • 基础功能:
      • getID() 直接return id;
      • getValue()直接return value;
  • Equipment

    • 基础功能:
      • getID()直接return id;

有了这些之后,我们就可以开始撰写Main也就是主函数的内容了。
首先是要对输入进行解析(这里直接套用第二次作业的介绍里给的范例):

1
2
3
4
5
6
7
8
ArrayList<ArrayList<String>> inputInfo = new ArrayList<>(); // 解析后的输入将会存进该容器中, 类似于c语言的二维数组
Scanner scanner = new Scanner(System.in);
int n = Integer.parseInt(scanner.nextLine().trim()); // 读取行数
for (int i = 0; i < n; ++i) {
String nextLine = scanner.nextLine(); // 读取本行指令
String[] strings = nextLine.trim().split(" +"); // 按空格对行进行分割
inputInfo.add(new ArrayList<>(Arrays.asList(strings))); // 将指令分割后的各个部分存进容器中
}

如果输入:
1
2
3
4
5
6
7
6
aa Alice
ab Alice HealPotion 50
ab Alice ManaPotion 100
ae Alice IronSword
rb Alice HealPotion
re Alice IronSword

这时候我们的指令就被存在二维数组里了,相当于:
1
2
3
4
5
6
7
8
inputInfo[][]={
"aa" ,"Alice";
"ab" ,"Alice" ,"HealPotion" ,"50";
"ab" ,"Alice" ,"ManaPotion" ,"100";
"ae" ,"Alice" ,"IronSword";
"rb" ,"Alice" ,"HealPotion";
"re" ,"Alice" ,"IronSword";
}

这时候我们就可以根据解析后的命令来操作了。
这里给出一个基本的框架:

1
2
3
4
5
6
7
8
9
10
11
12
for (ArrayList<String> instruction : inputInfo) {
String command = instruction.get(0);
switch (command) {
case "aa": {
//do something
break;
}
/*省略其它命令*/
default:
break;
}
}

而处理命令时我们有一个基本的流程,
这里以ae Alice IronSword为例,
先在储存Adventure容器中找到Alice,
然后找到它的Equipment容器,
再调用函数进行操作。
这样就保证了我们确实将IronSword
添加到Alice的装备库里了,
而不是添加到某个不知道在哪的Alice的装备库里。

这时候我们就发现我们需要添加几个根据Id
在容器里找到要操作元素的函数:

  • MAin
    • getAdventure
  • Adventure:
    • getBottle
    • getEquipment
      使用for循环遍历容器,注意比较Id
      找到后返回相应的元素,否则返回null

当我们顺利地完成之后,会发现主函数过于臃肿了。
rb移除药水瓶为例:

1
2
3
4
5
6
7
8
9
10
11
case "rb": {
String adv = instruction.get(1);
String botId = instruction.get(2);
Adventure adventure = Main.GetAdventure(advArrayList,adv);
Bottle bottle = adventure.GetBottle(botId);
if (bottle != null) {
adventure.RemoveBottle(botId);
System.out.println(adventure.GetBottleNum() + " " + bottle.getEffect());
}
break;
}

而方法里只有一行!:
1
2
3
public void RemoveBottle(Bottle bottle) {
botArList.remove(bottle);
}

这不符合我们要求主函数尽量简洁的原则,同时这个方法似乎也没有存在的必要。

因此我们修改一下接口,将寻找的过程也放在方法里,同时将找到要删除的bottle后的过程提取出来变成方法(主要是删除和输出部分)。

这样修正后的代码就是这样!
是不是减少了很多!

1
2
3
4
5
6
7
8
case "rb": {
String adv = instruction.get(1);
String botId = instruction.get(2);
Adventure adventure = Main.GetAdventure(advArrayList,adv);
Main.printBot(adventure,botId);
//这里更进一步,也可以将找adventure的代码放到print里,但是作者当时没有发现hh
break;
}

这是优化后的remove函数:
1
2
3
4
5
6
public void RemoveBottle(String botId) {
Bottle bottle = GetBottle(botId);
if (bottle != null) {
botArList.remove(bottle);
}
}

这是提取出来的print函数:
1
2
3
4
5
6
7
private static void printBot(Adventure adventure,String botId) {
if (adventure != null) {
Bottle bottle = adventure.GetBottle(botId);
adventure.RemoveBottle(botId);
System.out.println(adventure.GetBottleNum() + " " + bottle.getEffect());
}
}-

根据这个思想,我们对剩下的方法接口也进行一些完善:

  • Main
    • 添加Adventure(AdventureList,adventureID)
  • Adventure
    • 添加/删除bottle (bottleId,bottleValue)
    • 添加/删除Equipment(EquipmentId)
      这样主函数也更加简洁了!

运行之后发现没有什么问题!
至此我们的程序设计就告一段落。
接下来就是应用Junit来进行测试。