Mondrian

导航

Java第7~8次大作业及期末总结

一、前言

1.知识点

对于这两次电路题,重要的是匹配和按格式分组

关于字符串的匹配和分组,重要的是如何按照一定的格式,将一整串字符当中有用的信息提取出来,所以这里推荐使用正则表达式。正则表达式是一种用来描述字符串模式的工具。Java提供了强大的正则表达式支持,可以用来匹配、查找、替换、分割字符串等操作。关于正则表达式的使用,其主要涉及以下几个类:

  • Pattern:正则表达式的编译表示。
  • Matcher:用于执行匹配操作的引擎。

下面是一个简单的使用实例流程:

String regex = "\\d+";//匹配数字
String input = "There are 123 apples.";
        
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(input);
        
if(matcher.find())
{
	System.out.println("Found: " + matcher.group());
}
else
{
	System.out.println("No match found.");
}

除过正则表达式,使用最多的就是继承了,尤其是在现在电路越来越多的情况下,好的继承提供了一定的可扩展性,迭代起来更加方便,管理起来也更加方便。关于继承的相关知识点如下:

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

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

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

例如在电路程序当中,很多电器都有相同的属性,如果将电器分开来建立类的话,可能会变得非常复杂,并且管理起来不方便,比如第二次迭代新加入“电阻”时,如果分开来建立,那就要每个类都要修改,但是如果使用了继承的话,则只需要将父类做修改即可。
除了相同的属性外,也能写一些相同的方法来增加可扩展性:

abstract class Device
{
    protected String deviceId;
    protected double inV;//输入电压
    protected double outV;//输出电压
    protected String lastDevice;//上一个设备
    protected String nextDevice;//下一个设备
    protected double Resistance;//电阻
    public Device(String deviceId, double inV, double outV, String lastDevice, String nextDevice, double Resistance){
        this.deviceId = deviceId;
        this.inV = inV;//初始输入电压为0
        this.outV = outV;//初始输出电压为0
        this.lastDevice = lastDevice;
        this.nextDevice = nextDevice;
        this.Resistance = Resistance;
    }
    public String getDeviceId(){
        return deviceId;
    }
    public String getLastDevice(){
        return lastDevice;
    }
    public String getNextDevice(){
        return nextDevice;
    }
    public double getResistance(){
        return Resistance;
    }
    abstract double getInV();//抽象方法,获取输入电压
    abstract double getOutV();//抽象方法,获取输出电压
}

当然,除了电器,我在这里将电路也定义了一个类出来,很多同学都是将电路看作是电器的继承,因为按大体情况来看的话,电路也有电阻、电压、电流,也有输入输出,确实看做一个大的电器处理起来比较方便,但是我认为应按照物理属性的不同将它们分开来,电路相比于电器,拥有更多的单独属性,比如电路中电器的数量、包含各个电器的电阻电压表、电器信息等等,并且电路拥有更多的方法,比如电路展示、电路构建、内部电器排序等等,所以单独构建一个类是有必要的,串并联继承起来也方便,我在这里也新继承了一个“超级电路”,用来集成各个子电路,相当于总电路:

//基类:电路类
abstract class Circuit
{
	//...
}
class ParallelCircuit extends Circuit
{
	//...
}
class SeriesCircuit extends Circuit
{
	//...
}
class SuperCircuit extends Circuit//集成所有电路信息,在这里构建每一条子电路
{
	//...
}

2.难度

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

(1)第三次电路程序相较于第二次,加入了互斥开关和受控窗帘,对于窗帘没什么好说的,当做普通电器处理,很方便,比较难处理的是互斥开关,曾经的“lastDevice”和“nextDevice”属性已经不再适用。本次还有一处最大的改动,那就是多个并联电阻的加入,如果上次的电路继承写的比较好的话,还是好处理的。

(2)第四次电路程序相较于第三次,新增加了元件二极管,这表示了不能再根据分压直接来做,而是要根据二极管的方向来考虑电路是否是通畅的。新增了电路引脚电压显示,所有在使用分压来做的时候要注意元件的方向,尤其是涉及互斥开关的时候。新增电流限制,使用分压来做的时候要考虑电流,所以最终还是回归了电流的计算,这一点倒是好处理,在每个类里新增一种判断方法即可。对于串联并联电路混合串并联的,都能从最外层开始递归往里循环依次处理,本次题目难处理的点就在于二极管的判断以及短路检测。

二、设计与分析

1.第三次电路程序

对于新增加的受控窗帘,其本质也是电器,所以再新继承一个电器类即可,唯一改动的地方就是电阻及输出电压的计算方法。对于互斥开关,由于之前设计上的缺陷,导致“lastDevice”和“nextDevice”属性不能再直接使用,对于添加标志位进行判断连接在哪里,也不好处理,所以我在电路中加了一个“互斥开关标识表”,用于记录当前互斥开关连接的是哪里,本次题目
类图如下:

总体的处理流程改动不大,除了新增两个电器的信息录入外,在计算电路电阻时,新增对互斥开关的特殊处理,即检查它闭合的是哪个引脚、是否在本条电路当中等等,以此来计算电阻,以及在最后构建电路时,检查电路是否闭合的过程中,添加对互斥开关的检查和处理,总体处理流程如下:

对于新增加的互斥开关和受控窗帘,在dealWithDeviceInformation( )函数中新增对互斥开关“H”的处理,在各个子电路的处理当中新增对受控窗帘“S”的处理:

//dealWithDeviceInformation( )函数中的新增处理
else if(charArray[1] == 'H'){
	String HscID = String.valueOf(charArray[2]);
	circuit.setHcs(HscID);
	for(int i = 0; i < circuit.seriesCircuits.size(); i++)//串联子集的设备同步更新状态
	circuit.seriesCircuits.get(i).setHcs(HscID);
}
//子电路当中的新增处理:
else if(deviceName.equals("S")){
	for(int j = 0; j < curtains.size(); j++){
		if(curtains.get(j).getDeviceId().equals(deviceID))
			resistance += curtains.get(j).getResistance();
	}
}else if(deviceName.equals("H")){
	for(int j = 0; j < hcs.size(); j++){
		if(hcs.get(j).getDeviceId().equals(deviceID)){
			if(hcs.get(j).getResistance(HCSlinkFoot.get(i)) != -1){
				resistance += hcs.get(j).getResistance(HCSlinkFoot.get(i));
			}else{
				return -1;//电路外开关断开
			}
		}
	}
}

计算电阻这里,新增对“S”和“H”的处理,其中“H”的处理较为特殊,从互斥开关闭合表当中寻找闭合引脚,对电阻进行计算处理,如下所示:

else if(deviceName.equals("H")){
	for(int j = 0; j < hcs.size(); j++){
		if(hcs.get(j).getDeviceId().equals(deviceID)){
			if(hcs.get(j).getResistance(HCSlinkFoot.get(i)) != -1){
				resistance += hcs.get(j).getResistance(HCSlinkFoot.get(i));
			}else{
				return -1;//电路外开关断开
			}
		}
	}
}

2.第四次电路程序

相比于第三次程序,本次程序添加了很多东西。

  • 首先是二极管,作用相当于一个不可调的开关,即初始状态正向导通则就是一个闭合开关,初始状态反向截止时,相当于打开的开关;
  • 对于管脚电压的显示,在电器类当中新增添两个属性,表示引脚电压的信息,入引脚的电压可由原来的分压方法进行计算,出引脚的电压则可由下一个设备进行提供(下一个设备的入引脚电压,以此类推,直到整条电路结束);
  • 电流限制比较好处理,在类当中新增一个“threshold”属性,由其继承的子类进行数据的填入,然后在电路的“showAllDevices()”方法当中进行一个判断的输出即可;
  • 对于并联当中包含并联的现象,做法就是扩展之前的“超级电路”的功能,让“子串联”和“子并联”也具有“递归处理电路”的方法,因为有建立“电路设备表”,所以每条电路都知道自己是否包含子电路,直到遇到只包含电器设备的子电路,代表已经到最深层的子电路;
  • 对于短路检测,目前想到的是根据电路电压和电阻信息来判断当前路是否为无电阻设备的通路,最后对最大的串联电路进行检查,判断其每个串联模块是否都为无电阻通路,若全部都是则该电路电流无穷大,判定为短路。

类图如下:

在处理上,除了将原来的“处理子电路”修改为“使用递归方式处理子电路”外,在分压结束后执行“短路检查”方法,根据检查的信息判断下一步输出该怎样处理,程序主要执行的大体流程如下:

对于电路设备的信息的处理,继续使用一个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')
            {
                //更改总电路设备状态
                //更改子电路设备状态
            }
            else if(charArray[1] == 'H')
            {
            	//串联子集的设备同步更新状态
            }
        }
    }

三、踩坑心得

1.电路程序3

这次题目由于加入了一个未曾设想过的三脚元件,导致程序编写的很慢,曾经的“LastDevice”和“NextDevice”属性不能在此上继续使用。踩坑最多的地方也是这个三脚开关,在一遍遍尝试下,我使用了一种新的表“HCSlinkFoot”,把它作为电路类的一个属性,用以记录当前电路当中电器所参与串联的引脚编号,这样一来,只需要在所有操作都完成之后(即电器创建与开关、调速器类的电器改变完状态),在创建电路的方法当中,根据“HCSlinkFoot”中三脚开关初始接入电路的引脚和该三脚开关当前的“closedFoot”进行比对即可判断电路的闭合,相当于将一个三脚开关拆分成两个两脚开关,分别接入两个电路当中,只不过一个电路中的闭合时另一个电路中的要断开。

//checkCircuit()方法中对于三脚开关的处理
for(int i = 0; i < deviceIds.size(); i++)
{
      if(deviceNames.get(i).equals("H"))
      {
          for(int j = 0; j < hcs.size(); j++){
              if(hcs.get(j).getDeviceId().equals(deviceIds.get(i).toString())){
                  if(hcs.get(j).getResistance(HCSlinkFoot.get(i)) != -1){
                      isClosedNum2++;
                  }
              }
          }
      }
 }

对于三脚开关,因为我把它拆成了两个开关,所以同一个开关要出现在两个电路中时,添加时要做特殊处理:

if(!linkFoot.equals("IN") && !linkFoot.equals("OUT") && HCSlinkFoot.size() < deviceIds.size())
{
    int linkFootTemp = Integer.parseInt(linkFoot);
    if(deviceName.equals("H") && linkFootTemp != 1){
        HCSlinkFoot.add(linkFootTemp);
    }
    if(!deviceName.equals("H")){
        HCSlinkFoot.add(linkFootTemp);
    }
}
else if(HCSlinkFoot.size() < deviceIds.size())
    HCSlinkFoot.add(0);

最后提交时处理好两个开关的判断就能拿满分了

2.电路程序4

在写前几次程序时,我的代码量很大,比如第二次电路程序时就到了1200行,第三次时更是有1500行,这是我设计上的缺陷,因为考虑到很多潜在bug,所以写了很多,但是后面的弊端也暴露了出来:由于职责分配做的不是很好,所以在题目改动需要增加新东西时,改的地方要很多,扩展性些微差点,所以这次题目并没有做好。

四、改进建议

代码永远没有最完整的,总会不断地出现问题,比如说上次提到的SuperCircuit类,由于要处理串并联电路,所以SuperCircuit中又新增了许多方法,使得整个类的功能非常丰富,但是更加臃肿,而且方法几乎无法单独拿出来形成一个新类,职责分配做的不是很好。我对未来我的改进建议是:

  1. 继续保持“先设计再实现”的习惯
  2. 合理规划类的职责,避免出现非常臃肿的现象
  3. 设计时充分考虑代码的可扩展性,尽量避免出现改一个小部分要连贯改很多其它地方的现象

五、学期总结

这学期学习了Java,让我了解和体会到了“面向对象编程”的便捷性,也让我知道了“封装”的概念。我觉得对我帮助最大的就是这几次大作业,让我深刻理解了Java中一些类的使用,帮助我更深的理解了“面向对象”的概念。在这个过程中,我的“设计能力”和“逻辑能力”得到了很大的锻炼,这让我在面对一些复杂的问题时能够更从容地应对和处理,这无疑对我未来的发展提供了很大的帮助。

posted on 2024-12-28 21:02  克里斯琴  阅读(31)  评论(0)    收藏  举报