Mondrian

导航

Java大作业第4~6次的综合性总结

一、前言

1.知识点

这三次题目里,所涉及的字符串匹配问题,侧重的应用不同。
对于答题判题程序,侧重点在于字符串的纠错匹配,因为会有不合法输入
对于后面的电路题,侧重点在于匹配和按格式分组(目前题目里没有错误输入处理)

答题判题程序4的字符串类型变化丰富,比较复杂,相较于第3次的来说其实变化不大,第四次的重点在于类的继承,不止第四次,目前两次电路程序涉及最多的也是类的继承,它允许一个类继承另一个类的属性和方法。通过继承,子类可以重用父类的代码,并且可以根据需要扩展或修改这些代码,其基本语法为:

class 子类名 extends 父类名
{
    //子类特有的属性和方法
}

类的继承拥有以下特点:

  • 属性继承
    子类可以继承父类的实例变量(属性)。
    子类可以直接访问父类的非私有(private)成员,如protected、public等。
    如果子类需要访问父类的私有成员,可以通过父类提供的公有方法来访问。
  • 方法继承:
    子类继承父类的方法,但如果需要,可以重写(Override)父类的方法,提供更适合子类的实现。
    子类可以直接调用父类的公共方法。
  • 构造函数
    子类不会继承父类的构造函数,但可以通过super()调用父类的构造函数。
    如果父类没有无参构造函数,子类必须显式调用父类的构造函数,并传递相应的参数。

在实际使用的过程中,我们常使用的是这三个关键字:

  • extends:用来表示继承关系。
  • super:用来访问父类的成员变量或方法,或调用父类的构造函数。
  • this:用于访问当前对象的成员变量和方法。

例如在答题判题程序4中,新增加了选择题和填空题,而它们相较于原始题目,只增加了一些新的方法,属性都是一样的:

protected int number;//题目编号
protected String content;//题目内容
protected String standardAnswer;//标准答案
protected boolean stata;//题目状态,true表示已添加,false表示被删除

所以可以使用entends来进行继承,然后添加新的属于自己的特殊方法。在构造时,由于属性和父类完全相同,故在构造方法中使用super:

//选择题
class Question_Select extends Question
{
    public Question_Select(int number, String content, String standardAnswer)
    {
        super(number, content, standardAnswer);//调用父类构造函数
    }
    //...
}
//填空题
class Question_Fill extends Question
{
    public Question_Fill(int number, String content, String standardAnswer)
    {
        super(number, content, standardAnswer);//调用父类构造函数
    }
    //...
}

继承会用之后,后面的电路题也会方便很多。

2.难度

大作业都是循序渐进的,题目难度不仅和本身内容有关,还和之前的设计有关。

(1)第四次答题判题程序新增两个题目类型,需要用到继承,字符串处理没多大变化,和之前三次的差不多。

(2)第一次电路程序不算很难,大概就是几个元器件,而且都是串联的,也不涉及什么错误输入,一行就一个链接信息,所以只需要使用contains()方法匹配指定字符然后判断就行。

(3)第二次电路程序相较于第一次,新增了并联电路,而且一条电路信息是整个输入完的,单使用contains()方法很难写,需要使用正则表达式来对字符组“[ ]”里的内容进行匹配。

二、设计与分析

1.第四次答题判题程序

通过读题我们发现,新增加的两个类“选择题”和“填空题”其实属性和“Question”是一样的,只不过有了各自的新的方法(例如checkAnswer()),所以在这里我们使用继承来实现,继承父类的基本方法和属性。同样的,在这次程序中,由于选择题和填空题处理答案的方式不同,所以在继承中重写checkAnswer()方法。
新增的类图如下:

总体的处理流程也是在上次的基础上又加了两种题目的处理而已,由于题目、答题信息等均为乱序输入,所以我们在整体信息全部录入完成之后再对其进行处理,总体处理流程如下:

相比于前三次程序,这一次的程序将输入的信息种类增加到了7种,有了前几次的处理经验之后,这次其实就是扩展原有功能,以我的设计框架作为分析对象,添加内容如下:
在输入信息的录入部分,添加处理两种新题型的分支:

        else if(qSMatcher.matches())//此处处理选择题信息题目信息 #Z:
        {
            makeSelectQuestionInformation(count, questionSelects, str);//将选择题信息存入
            infirmationFlag = true;
        }
        else if(qFMatcher.matches())//此处处理填空题信息题目信息 #K:
        {
            makeFillQuestionInformation(count, questionFills, str);
            infirmationFlag = true;
        }

在遍历试卷题目这里,新增两种题型的寻找处理:

if(answerFindFlag == false) System.out.println("answer is null");
else//进入这个else之后,开始对每个题目进行检索(普通题、选择题、填空题)
{
	//int questionOrderNum = testAnswer.getQuestionOrderNum(j);//获取题目顺序编号
	int tempQuestionNum = testPaper.getQuestionNum(j);//获取题目编号
	boolean findFlag = false;
	if(!findFlag) findFlag = findFromQuestion(testAnswer,testPaper,questions);//从普通题中寻找,找到就处理,同时返回状态码
	if(!findFlag) findFlag = findFromSelectQuestion(testAnswer,testPaper,questionSelects);//从选择题中寻找,找到就处理,同时返回状态码
	if(!findFlag) findFlag = findFromFillQuestion(testAnswer,testPaper,questionFills);//从填空题中寻找,找到就处理,同时返回状态码
	if(findFlag == false) System.out.println("non-existent question~0");
}

2.第一次电路程序

初次见到这个程序时确实是一头雾水,这就需要我们将题目多读几遍,把样例多看几遍,就能看出解题的思路了。第一次题目拥有三个控制设备和三个受控设备,控制设备的作用主要是调节电路电压(调速器)以及控制电路通断(开关),而受控设备则是根据电压差来进行工作,他们都有一个共同属性,那就是电路设备,所以我们写一个抽象父类CircuitDevice,由于各个电路设备对于输入输出的电压处理不同,所以里面包含两个抽象方法“getInputVoltage()”和“getOutputVoltage()”,由子类实现,在第一次题目中,受控设备都没有电阻,而且调速器只可能接在VCC上,所以处理起来非常简单,没有调速器的情况下只需判断电路所有开关是否闭合即可,然后所有设备都分配满电压。有调速器时,在“给设备分电压”的这一步前先根据调速器设置好输入电压,然后继续上面的步骤就好了。
考虑到后续会有新的电路。比如并联电路、整个串并联电路等。若是都继承Device则显得过于庞大了,所以我单独创建了一个Circuit类,然后将其它设备都聚合起来,一个Circuit对象就代表了一段完整的电路。同时提供对内部调速设备、开关设置的方法。
类图如下:

总体处理流程如下:

对于电路设备的信息的处理,我全部使用一个dealWithDeviceInformation()方法来处理,只传入一个字符串,然后根据字符匹配来进行相应的处理,在方法内部,首先会进行“连接信息”和“控制信息”的判断,然后再在分支里进行小模块的判断,对于连接信息,通过首尾判断后,将设备添加到设备列表当中,首尾判断的目的是设置电压输入端和输出结束端,对于电路设备的添加,统一使用addDevice()方法来进行设备的添加,addDevice()方法会自动判断该设备是何种设备,以及判断该设备是否已经被添加,连接信息的处理分支如下:

if(ctMatcher.find())//连接信息
{
	String Content1 = ctMatcher.group(1);
	String[] Content = Content1.split(" ");
	//VCC和GND要特殊处理
	if(Content[0].equals("VCC"))//第一条消息
	{
		//此处提取设备类型和编号
		circuit.addDevice(letter, number);
	}
	else if(Content[1].equals("GND"))
	{
		//此处提取设备类型和编号
		circuit.addDevice(letter, number);
	}
	else
	{
		//此处提取设备类型和编号
		circuit.addDevice(letter, number);

		//此处提取设备类型和编号
		circuit.addDevice(letter2, number2);
	}
}

对于控制信息,我在Circuit类中设计了三个方法用以对电路内的控制设备进行设置,在判断到信息为控制信息后,匹配第二个字符来判断是何种设备的控制信息,然后在分支中提取到对应的信息(如设备ID、调速器的调速信息)后,调用相应的set函数,改变电路设备的状态,控制信息的处理分支如下:

else if(clMatcher.find())//控制信息
{
	// 获取匹配到的字符串
	String matchedString = clMatcher.group();
	// 将字符串转为字符数组
	char[] charArray = matchedString.toCharArray();
	if(charArray[1] == 'K')
	{
		String switchID = String.valueOf(charArray[2]);
		circuit.setSwitch(switchID);
	}
	else if(charArray[1] == 'F')
	{
		String gearSpeedControllerID = String.valueOf(charArray[2]);
		String gearOperate = String.valueOf(charArray[3]);
		circuit.setGearSpeedController(gearSpeedControllerID, gearOperate);
	}
	else if(charArray[1] == 'L')
	{
		String continuousSpeedControllerID = String.valueOf(charArray[2]);
		String continuousSpeedControllerPosition = new String(charArray, 4, charArray.length - 4);
		circuit.setContinuousSpeedController(continuousSpeedControllerID, continuousSpeedControllerPosition);
	}
}

3.第二次电路程序

相比于第一次程序,本次新添加了并联电路,并且将电路的链接信息变为了一整行输入,所以我们必须将对输入信息的处理逻辑进行修改,不仅要做到精准判断,还要在一行信息当中提取出我们需要的连接信息(即“[ ] ”内的内容)。本次题目新加的并联电路暂时只有一个,但考虑到后面的迭代会添加很多,所以还是将并联电路单独继承一个类出来比较好处理,然后再继承一个SuperCircuit类来将串并联电路进行集成,方便后续处理。
对于新添加的电阻,在CricuitDevice类里扩展即可,同样的,由于我们要将整段电路当做是一个电路设备来处理,所以在Circuit类中也要添加Resistance属性,并提供get方法。特殊的,对于串联和并联电路,我还提供了一个电阻的计算方法,写在了get方法中,每次调用时就会计算一次,这样做是为了保证在后面的控制信息接收到后,不用每接收一次就重新计算整个电路的电阻,而是在所有操作都完成、需要用到电阻的时候再进行计算。
由于我们现在将整个电路看做了一个Device,所以在添加设备时,不同电路的处理逻辑不同,例如普通的串并联电路只需要添加设备即可,但包含串并联电路的SuperCircuit则则不能继续使用addDevice()来添加。所以我在addDevice()方法上提供了新的方法:updateDeviceList(),该方法用于将电路和设备都添加进来,addDevice()方法包含在里面被调用。
类图如下:

程序主要执行的大体流程(基于第一次程序做的修改):

对于电路设备的信息的处理,还是使用一个dealWithDeviceInformation()方法来处理,再在该方法当中根据不同的电路信息进行不同的分支处理,函数结构如下:

    public void dealWithDeviceInformation(SuperCircuit circuit,String str)
    {//处理所有输入的设备信息
        /**
        *此处为正则表达式的匹配和处理
        */
        //...
        if(seriesMatcher.matches() && str.contains("VCC"))
        {//总电路信息 /*总电路中只将整体信息存入设备列表,单个的设备(包含在串、并联电路中的)将不再计入总电路设备*/
        	//此处对总电路信息进行分割提取
        	//...
            while (matcher.find()) 
            {
                String Content1 = matcher.group(1);//提取方括号内的内容
                String[] Content = Content1.split(" ");//将内容按空格分割
                if(Content[0].equals("VCC"))
                {//第一条消息
                    //更新设备列表和实际设备信息
                    //...
                    circuit.addDevice(letter, number);
                    circuit.updateDeviceList(number, letter);//更新电路设备列表
                }
                else if(Content[1].equals("GND"))
                {
                    //更新设备列表和实际设备信息
                    //...
                    circuit.addDevice(letter, number);
                    circuit.updateDeviceList(number, letter);//更新电路设备列表
                }
                else
                {
                    //更新设备列表和实际设备信息
                    //...
                    circuit.addDevice(letter2, number2);
                    circuit.updateDeviceList(number2, letter2);//更新电路设备列表
                }
            }
        }
        else if(seriesMatcher.matches() && str.contains("IN"))
        {//串联电路信息
        	//此处对总电路信息进行分割提取
        	//...
            while (matcher.find()) 
            {
            	//分割
                if(Content[0].equals("IN"))
                {//第一条消息
                    //更新串联设备列表和实际设备信息
                    //...
                }
                else if(Content[1].equals("OUT"))
                {//末节点
                    //更新串联设备列表和实际设备信息
                    //...
                }
                else
                {//中间结点
                    //更新串联设备列表和实际设备信息
                    //...
                }
            }
        }
        else if(parallelMatcher.matches())
        {//并联电路信息
        	//此处对总电路信息进行分割提取
        	//...
            for(int i = 0; i < Content.length; i++)
            {
                //更新串联设备列表和实际设备信息
                    //...
            }
        }
        else if(clMatcher.find())
        {//控制信息
            String matchedString = clMatcher.group();// 获取匹配到的字符串
            char[] charArray = matchedString.toCharArray();//将字符串转为字符数组
            if(charArray[1] == 'K')
            {
                //更改总电路设备状态
                //更改子电路设备状态
            }
            else if(charArray[1] == 'F')
            {
                //更改总电路设备状态
                //更改子电路设备状态
            }
            else if(charArray[1] == 'L')
            {
                //更改总电路设备状态
                //更改子电路设备状态
            }
        }
    }

三、踩坑心得

1.答题判题程序4

得益于前几次的踩坑,这次程序已经能应付大部分潜在bug,所以写好新题目的扩展和处理后,提交没有任何问题,一次测试点全部通过,没有踩坑。

2.电路程序1

有了前几次在设计上踩坑的经历,在写这次程序时,我并没有直接开写,而是现在纸上分析了很久,最终确定了一个合适的解决方案。但尽管如此,还是出现了潜在的问题,首先是第一次提交后发现的问题:

从上往下慢慢想,看到提示时我明白了,这是整个电路只有一个设备,VCC直连进,GND直连出,于是赶快写了个测试样例试了一下,非零返回,打开代码一看,发现是makeCircuit()方法里连接电路的函数出了问题:

if(i == 0)
{
	lastDevice = "VCC";
	nextDevice = deviceNames.get(i + 1) + deviceIds.get(i + 1);
	inputVoltage = 220;
	outputVoltage = 0;
}

在这里由于不存在nextDevice,所以访问时会越界,从而非零返回,于是我在第一个设备里新添了一个判断,保证不会访问越界:

if(i == 0)
{
	lastDevice = "VCC";
	if(i == deviceIds.size() - 1) nextDevice = "GND";
	else nextDevice = deviceNames.get(i + 1) + deviceIds.get(i + 1);
	inputVoltage = 220;
	outputVoltage = 0;
}

上面三个测试点算是过了:

下面这四个测试点,我测试了很多例子也没过,于是我想到了从题目当中来获取信息,再次读题时发现了一条规则,那就是相同设备需要排序后再输出,于是在showAllDevices()方法中添加sort方法:

public void showAllDevices()//显示所有设备信息
{
	sortSwitchs();
	sortGearSpeedControllers();
	sortContinuousSpeedControllers();
	sortIncandescentLamps();
	sortFluorescentLamps();
	sortCeilingFans();
	//输出设备信息
	//...
}

再次提交后果然没错了:

3.电路程序2

有了上一次读题的经历,这一次我反反复复将题目读了好多遍,还是遵循“先设计再编码”的原则,设计草稿画了3天,最后用一填时间来编码,果然,“最好的测试点都藏在题目里”,把题目研究明白无遗漏后,样例全过就是水到渠成的事了。

四、改进建议

由于上次踩了很多坑,所以这次开始,我更加注重于设计逻辑,保证草稿上的东西能行得通,再去实际实现,然后在实现的过程中再慢慢发现问题、解决问题。随着不断地进步,旧的问题解决了,也会出现新的问题,比如说虽然这次main中的代码非常少,但我的Circuit类显的过于庞大。尤其是继承后的SuperCircuit类,由于要处理串并联电路,所以SuperCircuit中又新增了许多方法,使得整个类的功能非常丰富,但是比较臃肿,这导致后面出现了新问题,那就是代码长度超限的问题,写完电路程序2后,总共有1300+行,提交不了,于是我将其缩减到了1000+行后才能提交。后面问了老师后,老师说是按照字节来算的,所以我超长的原因可能是变量名函数名太长,基于这些问题,我对未来我的改进建议是:

  1. 继续保持“先设计再实现”的习惯
  2. 类的职责可以再进一步的进行细分,这样不会显得类很臃肿
  3. 既然用了继承,那就多考虑子类中是否有相同方法,进行归一
  4. 尽量使用简短但易懂得变量名以及函数名
  5. 继续考虑代码的可扩展性,避免“牵一发而动全身”

五、总结

写了这几次大作业后,我意识到了很多方面的重要性,也确实从中得到了好处。比如答题判题程序4让我意识到了可扩展的重要性,在这次题目当中,我几乎没有写很多东西,就是添加了两个新题目进去,也没有出很多错,测试点一遍全部通过。正是因为之前的代码分工较明确,之间的关联也很少,而且改修的bug也修了,所以这次才会写的比较轻松。
而电路程序则是让我意识到了读题和设计的重要性,第一次电路程序就是因为读题不仔细,遗漏了“排序”这一点,导致后面4个测试点卡了好久,等第二次电路程序时,我便先反复读题分析,再进行设计,最后也没有卡任何测试点,我想这大概就是“仔细读题”带来的正反馈把。

posted on 2024-11-23 14:38  克里斯琴  阅读(36)  评论(0)    收藏  举报