OOpre总结

OOpre课程结课总结 ——22373362杜启嵘 ​ 经历九周的OOpre课程的学习,我对面向对象编程有了基本的认识。从类、对象、方法等的基本概念,到最后一次迭代作业涉及到的设计模式,我循序渐进地学习到基本的面向对象编程思想(虽然我的代码仍然不够面向对象QAQ)。 一.代码的最终架构和过程中的重构 ​ 在五次作业的迭代中,我进行了多次代码的重构,前几次作业中的重构我认为是合理的,最后一次作业中的重构是把超过500行的类中的部分方法强行抽离出来放进静态方法类,并把参数分成几行避免行字数超过100字(面向OO-checkstyle的重构)。 1.第一次重构 ​ 在第三次作业中进行了实际上是两次重构,第一次在面临主类中的主方法行数超过60行,对方法中分指令执行的代码抽离出来声明为单独的方法,在编写JUnit时进行了第二次重构,由于第一版代码中解析输入逻辑与代码执行逻辑杂糅,而在编写JUnit时无法对输入进行操作,改为使用课程组提供的利用“二维数组”在统一存储输入内容,在inputhandler类中读取二维数组进行指令解析,这样的架构就能编写满足覆盖率的JUnit,下图为简单的架构图。 二.第二次重构 ​ 第四次作业中新增了战斗日志的概念,我一开始的处理中并没有对fightlog建类,只是在Adventure中建立容器来存储代表战斗日志的字符串,导致处理逻辑比较复杂,结果因为一个方法中的错误逻辑挂了强测,在修改强测的过程中完成了对代码的重构,新增fightlog类,架构图如下 三.第三次重构 ​ 在最后一次迭代开发中,由于类的行数限制,我不得不将一个类拆成两个类,在静态方法类中进行传参,属于是一次很丑陋的重构。 2.使用JUnit的心得体会 ​ 使用JUnit可以在提交测评机之前进行本地测试,可以通过构造数据计算预期结果比对程序输出结果判断程序的正确性,在第六次作业完成过程中涉及到很多计算还有精度的问题,使用JUnit在本地进行测试可以找出一些问题。编写JUnit过程中达到分支覆盖对于验证正确性也有很大帮助,第六次作业中继承关系中覆盖不同子类进行测试帮助我找到了一些bug。在当下阶段使用JUnit的不足在于构造数据过于简单,没有对边界条件进行测试(毕竟手搓复杂数据真的很难绷QWQ),总体来说在几次作业的迭代中,我通过使用JUnit实现了对于程序的本地测试,并且能够发现一些bug,使第一次提交至少通过数据点多了一些。 3.学习OOpre的心得体会 ​ 从面向过程到面向对象的编程思维的转变对于我来说还是有一些难度,我的代码中的很多编写也不够面向对象,导致方法行数爆炸,类行数爆炸,处理逻辑复杂。但是在几次迭代中,我也对面向对象有了基本的认识 理解面向对象的核心概念:面向对象编程是一种基于对象的思维方式。它的核心概念包括类、对象、封装、继承和多态。要想掌握面向对象编程,首先要理解这些概念的含义和关系 不断练习和总结经验:面向对象编程是一种需要不断实践和经验积累的编程方式。通过不断地练习和实践,才能更好地理解和应用面向对象编程的技巧。同时,还要及时总结经验教训,找到自己的不足之处并加以改进 阅读和理解优秀的面向对象代码:课程结束后学习优秀代码 4.对OOpre课程的简单建议 提高中测强度,尽量中测程度的数据过了就不要挂强测(强测挂了真的好压力) 指导书中部分内容可以进行细化,尤其是第七次作业的指导书,对于不同设计模式的解释可以再细致一点(?)

November 4, 2023 · 1 min · sudo

OOpre_HW_5:常见bug分析

OOpre_HW_5:常见bug分析 ​ 本次作业的任务比较简单,对课程组给出的代码进行debug,只有中测,让我这种挂了强测的鼠鼠好欣慰。 一.输入解析类错误 ​ 常见的有scanner一类的函数,我们需要注意的无非以下几个函数的功能 1 2 3 1. scanner.next(); //读取下一个字符串 2. scanner.nextint();//读取下一个数字 3. scanner.nextLine();//读取下一行字符串 ​ 作业中出现了几次使用scanner.nextLine()方法读取下一个字符串的错误,这种错误还是比较明显的。 二.深克隆与浅克隆 ​ 在我的作业代码中,对于深克隆和浅克隆共出现了一个功能中的两次错误,即克隆小队的操作和克隆士兵的操作。 1.浅克隆 ​ 浅克隆即只对对象的引用进行克隆,换句话说是创建出来新的一个指针,与原对象指向相同的一块内存空间。本质上两个引用指向的是同一个实例,一个指针对对象进行修改,另一个指针进行访问时就会体现出这种修改。例如: 1 2 Team team1 = new Team(123456,"dqr"); Team team2 = team1; 2.深克隆 ​ 深克隆不仅要创建出新的引用,还要开辟出新的内存空间,本质上就是一个新的变量,只不过变量的构造方法中传入参数是需要被克隆的对象的参数。 1 2 Team team1 = new Team(123456,"dqr"); Team team2 = new Team(team1.getID(),team1.getName()); 三.相等比较 ==/equals ? ==是比较两个引用是否是同一个对象 equals为内容比较,比如名字,咒语等等 四.遍历容器:迭代器删除 ​ 对于容器中的元素删除,我们可能经常会用到“边遍历边删除的操作” ...

October 18, 2023 · 1 min · sudo

OOpre_HW_4强测修复&&代码架构重构

HW_4强测修复&&代码重构 ​ **OOpre_HW_4是我打的最快的一次!**然而在代码风格上却不够面向对象,而且代码业务逻辑上有问题,没有成功通过强测(只得了33分),在挂掉强测之后,我痛定思痛,决定先重构代码架构再进行逻辑修复。 一.代码架构 ​ 上图是课程组推荐的代码架构,回顾我的第一版代码,主要有以下两个问题: 输入解析逻辑放在main类中,导致main代码冗长 没有对fightlog进行建类,而是将fightlog作为附属于adventure的数据处理,导致代码结构耦合复杂 ​ 经过一晚上的代码构想和助教的交流,我将代码架构修改为下图: ​ 在这次的代码逻辑中,我将fightlog视作一个个与adventure同级的个体建类(这个是最重要的思想,想了好久),fightlog中存储战斗日志的模式,攻击时间、攻击者的名字,被攻击者的名字ArrayList,这里需要注意,对于ArrayList<String> attackedname,应当分情况存储 mode == 1,此时attackedname == null mode == 2,此时attackedname中只有一个元素 mode == 3,此时attackedname中包含所有被攻击者的元素 二.bug修复 1.正则表达式修复 ​ 正则表达式出错使得战斗日志输入解析错误,导致后续从二维数组中读取时出现NullPointerException,这就是强测第一次的报错,只能说第一篇博客发早了,传播了错误的正则表达式。下面附上通过强测的正则表达式 1 2 3 Pattern p = Pattern.compile("(\\d{4}/\\d{2})-([^@#-]+)-([^@#-]+)"); Pattern p1 = Pattern.compile("(\\d{4}/\\d{2})-([^@#-]+)@([^@#-]+)-([^@#-]+)"); Pattern p2 = Pattern.compile("(\\d{4}/\\d{2})-([^@#-]+)@#-([^@#-]+)"); 2.对于携带概念的再纠正 ​ 我们知道,在第三次作业中,我对于携带的处理是为每个物品设置一个becarreid属性,在后续的处理中,如“使用”等操作,都需要进行是否“携带”概念的判断,在这次作业中,我发现了上次强测没有测出来的bug,OP9()中对于同名装备进行替换时,没有判断是否携带,下面附上改正后代码: 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 public void OP9(int i) { int advId = Integer.parseInt(inputInfo.get(i).get(1)); int equId = Integer.parseInt(inputInfo.get(i).get(2)); Adventure man = adventures.get(advId); Equipment equipment = null; ArrayList<Equipment> equipments = man.returnequ(man); for (Equipment item : equipments) { if (item.getID(item) == equId) { equipment = item; break; } } String name = equipment.getName(equipment); //检查当前想要携带的装备是否存在同名 如果有则进行替换 Equipment equipment1 = null; for (Equipment item : equipments) { if (item.getName(item).equals(name) && item.getBecarried(item)) { //已经被携带的同名装备 equipment1 = item; break; } } if (equipment1 != null) { equipment1.reset(equipment1); } equipment.set(equipment); } ​ 需要注意的是&&item.getBecarried(item),这种错误我出现了两次了,下次一定要注意,名字符合的同时要判断是否携带。 ...

October 18, 2023 · 1 min · sudo

OOpre_HW4

OOpre_HW4 : 正则表达式 1.实现思路 ​ 这次作业实现思路上没有特别大难度(只新增了四条指令),但实际上作业体验下来相当于新增了一条指令,很多功能可以顺带着实现。即在我的做法中OP14()是进行战斗日志存储的方法,OP15(),OP16(),OP17(),只是将存好的战斗日志输出出来。 ​ 沿用“二维数组”的输入解析法,特判操作数为14时进行多行输入,引用变量row代表实际的行数(因为战斗日志不算在指令条数n内),利用正则表达式对输入的战斗日志进行解析,下面附上我的冗长的正则表达式 1 2 3 Pattern p = Pattern.compile("(\\d{4}/\\d{2})-([^\\s@#-]+)-([^\\s@#-]+)"); Pattern p1 = Pattern.compile("(\\d{4}/\\d{2})-([^\\s^@#-]+)@([^\\s@#-]+)-([^\\s@#-]+)"); Pattern p2 = Pattern.compile("(.*/.*)-(.*)@#-(.*)"); //这一条是助教改进的,还没太理解 ​ 之后按照题目叙述按部就班从二维数组中取出元素操作即可。这里我将战斗日志分为三个部分: ​ 注意:战斗日志的存储只能使用ArrayList只有这样才满有序性! 总表,在inputhandler中设置,在OP14()中读出后就将其加入总表,这样相当于沿着完整的时间线存入了战斗日志,对于OP15()的完成比较简单,只需要使用正则表达式从中提取出来,下面附上我的正则表达式(其实只需要对日期进行匹配) 1 Pattern p = Pattern.compile(date + ".+"); 下设在Adventure类中的attacklog和attackedlog分别记录这个人作为攻击者和被攻击的战斗记录,需要注意的是在实际操作中攻击者增加attacklog同时被攻击者要增加attackedlog。 ​ 沿着这个思路实现就好,但是助教说不够“面向对象”。(查我代码库QAQ)。 2.BUGS ​ 这次作业遇到的bug是我de时间最长的一次WWW.有很多粗心,也有一些逻辑上的不周到(第一遍写的时候没有反应过来),甚至还有笔误。这次作业我遇到的bug大部分都是输出错误,虽然要来回找很繁琐但是不值得记录,只有一个逻辑上的错误比较烦心,整整看了三个小时才通过比较AC输出调试出来,心态很崩 下面是错误代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public boolean useequ(Adventure man, Adventure man1,String name) { Equipment equipment = null; for (Equipment item : man.equipments) { if (item.getName(item).equals(name)) { equipment = item; break; } } if (equipment != null) { if(equipment.getBecarried(equipment)){ int level = man.level; man1.hitpoint = man1.hitpoint - equipment.getStar() * level; System.out.println(man1.getId(man1) + " " + man1.gethitpoint(man1)); return true; } } return false; } ​ 这种实现思路的错误之处在于:在我之前的迭代思路中,“背包”是一个概念而不是一个实体,在总库equipments中进行查找时,完全可能找到名字符合但是并没有携带的equipment(即但从名字找equipment不具有唯一性,可能会找错),这样就会使得永远也加不进去战斗日志,之前的迭代作业我们知道,一个人同名的装备只能有一件状态为carried,对于名字和是否携带的双重判断才是正确的逻辑。 ...

October 10, 2023 · 1 min · sudo

OOpre_HW3

OOpre_HW3 and JUnit 一.关于OO_checkstyle的新发现 只能采用驼峰命名法命名变量 方法行数不超过60行(后续重构代码将操作与处理输入分离的主要原理,虽然只有两分) 每行字数不超过100(方法传参时发现) 其余关于空格的问题省略 二.增量开发的思路 在此次作业中,新增了“食物”、“背包”等概念。food作为与equipment和bottle同级物品,背包则负责容纳这些物品。 新增操作: 尝试携带(放入背包)某物品(保证尝试携带的物品冒险者已经拥有) 尝试使用某物品(该物品必须被携带才能够使用) ​ 实现逻辑:我们需要明白“携带”与“使用”的业务逻辑。 我的第一版代码实现思路 ​ 我第一版代码中,按照题目描述,将food与package作为新建类处理,冒险者与背包之间的关系使用哈希表处理,建立起<advid,package>的映射,在背包中建立三个容器分别存储瓶子,装备和食物。对于加入背包,我的理解是,为冒险者增加物品是将物品放在冒险者对应的类adventure.java中对应的总库三个容器中,加入背包需要将物品从总库移动到与冒险者对应的背包,从物理角度来看是对物品进行了移动。这导致实现起来非常麻烦,例如统计数量等都需要考虑两个部分。这与题意不符,具体体现在中测最后一个数据点不过。 第二版代码 ​ 经过与助教的沟通,我理解到: 放入背包是一个概念的问题,而不是一个物理上的问题。放入背包并不需要将物品从总库中删除,只需要加入背包。 一开始处理中建立冒险者与背包对应哈希表的想法并不符合面向对象的逻辑,这是面向过程的思路,如果想要具体实现背包应该建立在冒险者类中 既然放入背包是一个概念问题,那么我们完全可以不去实现背包实体,而只需要进行概念上的判断。例如给每个物品增加一个属性 1 private boolean becarried ; 初始时设置为false即不在背包中,放入背包即建立方法将属性设置为true.这个思路在实现代码上是十分简便的,具体体验到的优势如下: 不需要新建数据结构存储放在背包中的物品 判断该物品是否在背包中只需要获取属性becarried 删除物品只需要在adventure类中的总库删除,实现简洁 获取物品数量是需要获取总库中的数量 三.代码架构与重构 ​ 经过checkstyle与JUnit对于代码架构的步步限制,我经历了三次代码重构,第一次是在编写过程中发现方法的行数不能超过60行,第二次是在传参时受到限制,选择将定义的静态方法从operation.java移动到inputhandler.java,第三次是编写JUnit过程中由于不能进行输入输出重定向等测评机认为的违法操作,这样只能将输入集中到一个类中,后续在方法中进行读取已经存储好的输入。 ​ 对于输入的类,原码如下 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import java.util.Arrays; import java.util.Scanner; import java.util.ArrayList; public class Main { public static void main(String [] args) { 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))); // 将指令分割后的各个部分存进容器中 } InputHandler inputHandler = new InputHandler(inputInfo); inputHandler.solve(n); } } ​ 这样所有的操作指令被以分割的字符串的方式存入inputinfo,后续将inputinfo传入inputhandler类进行处理,所有的变量从这个形式上的二维数组中读取。这样可以避免在编写JUnit时无法控制台输入导致无法测试方法导致覆盖率不够,第二部分任务寄掉的问题。 ...

September 22, 2023 · 2 min · sudo

OOpre_HW2

OOpre_HW2 第一次进行类的编写(冒险者故事的开端) 1.什么是面向对象(Object Oriented) ​ 对象能够直接反映现实生活中的事物,例如人、车、小鸟等,将其表示为程序中的对象,每个对象都有各自的状态特征(属性)以及行为特征(方法),除了可以存储数据外还可以对自身进行操作,相当于结构体与函数的封装。 ​ 面向对象就是把构成问题的事物分解成一个一个的对象,建立对象不是为了实现一个步骤,而是描述某个事物在解决问题中的行为。 ​ 类是面向对象中的一个很重要的概念,类是很多个具有相同属性和行为特征的对象所抽象出来的,对象是类的一个实例。 2. OO三大特征 封装 继承 多态 3.类与对象 ​ 类表示一个共性的产物,是一个综合的产物,而对象是一个个性的产物,类必须通过对象才可以使用,对象的所有操作都在类中定义 类由属性和方法组成 属性:特征 方法:行为 ​ 一个类想真正地进行操作则必须依靠对象 1 2 3 4 5 //对象的定义 classname objectname = new classname();//所有类的对象都是通过new关键字创建 //访问类中的属性或方法 objectname.id //访问属性 objectname.func(parameter1,parameter2)//调用方法 类的编写规则 类必须编写在.java文件中 一个.java文件中可以存在多个类,但只能存在一个public修饰的类 .java文件名必须与public修饰的类名相同 同一个包中不能有重名的类 4.构造方法 ​ 在创建对象时,调用构造方法,所有的JAVA类中都至少存在一个构造方法(除了主类),如果一个类中没有明确的编写构造方法,编译器会自动生成一个无参的构造方法,构造方法中没有任何的代码!如果自行编写了构造方法,则编译器不会生成无参的构造方法。 构造方法的定义格式 构造方法名称必须与类名相同 没有返回值类型的声明 1 2 3 4 5 6 7 //创建一个对象就要调用构造方法 //一个自定义构造方法的例子 public person(String name, int age) { this.name = name; this.age = age; } this关键字 this指当前对象 程序中非静态方法可以使用this关键字 指向当前代码运行时所处于的对象空间 引用当前对象的实例变量 目前只在构造方法中接触this关键字 static关键字 static修饰变量为静态变量,也成称为类变量,静态变量属于类本身,而不是属于对象实例。该类的所有对象共享同一个静态变量的值,不会开辟出多块内存空间,可以通过<类名>.<变量名>来访问静态变量,但此时的变量需要被public修饰而不是private ...

September 15, 2023 · 6 min · sudo

git指令的初步学习

面向OO的git指令 ​ 在秋季学期开设的OOpre课程中,我首次接触到使用Gitlab对代码进行版本管理,在第一次OOpre作业中我就受到了提交库的拷打,现在是2023年9月10日晚上22:46,开始编写面向OO的git指令 windows命令行操作经典指令 cd:change dictionary cd D: #只有转移磁盘需要加冒号: cd OO #进一步转移到目录下文件夹 cd .. #回退一层目录 ​ 2.pwd:print working dictionary 打印当前工作目录 ​ 3. git 指令 git是一个终端 提交注意 ​ 在每一次作业的发布中,分为作业发布区和个人仓库区,常见的操作是从作业发布区进行repository的clone,在本地完成作业后push到个人仓库区,需要注意的是公共发布区是没有push权限的,下面以第一次作业为例 完成作业的常见步骤 从公共发布区clone ​ 需要注意的是这里的每一个步骤都可以通过IDEA编译器操作或是通过git终端操作,这里我们主要介绍通过git的操作方式(我更加喜欢)。 ​ 进行源的clone,通过以下代码实现 1 git clone url path ​ 其中url代表远程仓库的地址如SSH密钥,path代表路径,绝对路径或相对路径均可以,如果指定的路径不存在则会创建该目录,笔者经过验证,在路径不存在的情况下,确实会进行生成。 需要注意的是在clone后已经存在.git文件 对代码进行修改后的一系列 常用指令git init git init指令用于生成本地的.git跟踪版本文件,是万恶的开端 git status git status用于查看当前文件的情况,是否被跟踪(tracked 用于代码管理),是否有修改等等 git add git add 指令是很常用的操作,用于将文件添加到tracked,常见的有这几种用法 1 2 3 4 //将.git目录下全体文件加入tracked git add . //添加单个文件进行版本管理 git add <filename>//其中如果filename中包含空格的话需要对文件名进行双引号处理 "filename" 通常对文件进行修改后或新建文件后需要进行git add 这时可以通过git status 进行文件状态的查询 ...

September 10, 2023 · 2 min · sudo