对题集“数字电路模拟程序”三次作业的总结
上次的随笔中我总结了关于航空器配重与货运管理系统的作业总结,而在本次随笔中,我将继续总结新题集---关于数字电路模拟程序的作业总结,反思这三次作业的不足之处。
前言
在第一次作业中,题给出要求设计基础的电路元件和电子配件,并对基础电子配件做出功能性要求需要我们实现,结果应该来讲是不尽人意的,首先第一个难点就是对于首次接触未给出类图的新手来说,自行设计电子配件类和电路元件类是一个巨大的挑战;第二个难点就是在设计完类后突然发现,对于元件的输入解析又是一大头疼之处,对元件的输入解析,其实就是对于字符串的操作,这显然十分考验编程者对于字符串操作的熟悉程度。
在第二次作业中,题新增了一个电子配件和三个电路元件,增加了电路配件意味着对于字符串的解析要有更多的情况,无疑也是在考核字符串操作。
第三次作业取消了第二次作业新增元件,取而代之的是增加子电路这个概念以及异常输入处理,由于题集较难,出题者给予了我们帮助,给出了设计建议。
设计与分析
第一次作业
目的:给出电路元件特征,编程实现数字电路模拟程序。
Pin类:负责存储输入的0、1电信号。
class Pin { String componentName; int pinNum; Pin(String fullPin) { int i = fullPin.lastIndexOf('-'); if (i == -1) { this.componentName = fullPin; this.pinNum = 0; } else { this.componentName = fullPin.substring(0, i); this.pinNum = Integer.parseInt(fullPin.substring(i + 1)); } } }
Component抽象类:将引脚组合起来,便于题中的5种门继承
abstract class Component { String componentName; List<Pin> input; int num; int output; Component(String name) { this.componentName = name; this.input = new ArrayList<>(); this.output = -1; this.num = extractNumber(name); } private int extractNumber(String name) { int i = name.length() - 1; while (i >= 0 && name.charAt(i) >= '0' && name.charAt(i) <= '9') { i--; } if (i + 1 >= name.length()) { return 0; } return Integer.parseInt(name.substring(i + 1)); } void addPin(Pin pin) { input.add(pin); } String getName() { return componentName; } abstract int analysis(List<Integer> list); }
AndGate类:继承Component,包含两个或多个输入引脚和一个输出引脚。所有输入引脚必须都是高电平,输出引脚才是高电平,只要有一个输入引脚为低电平,输出引脚输出低电平。
class AndGate extends Component { AndGate(String name) { super(name); } @Override int analysis(List<Integer> list) { for (int v : list) { if (v == 0) return 0; } return 1; } }
OrGate类:继承Component,包含两个或多个输入引脚和一个输出引脚。所有输入引脚必须都是低电平,输出引脚才是低电平,只要有一个输入引脚为高电平,输出引脚输出高电平。
class OrGate extends Component { OrGate(String name) { super(name); } @Override int analysis(List<Integer> list) { for (int v : list) { if (v == 1) return 1; } return 0; } }
NotGate类:继承Component,包含一个输入引脚和一个输出引脚。输出引脚的电平与输入引脚的电平相反,如输入为低电平,输出则为高电平。
class NotGate extends Component { NotGate(String name) { super(name); } @Override int analysis(List<Integer> list) { return list.get(0) == 0 ? 1 : 0; } }
XnorGate类:继承Component,包含两个输入引脚和一个输出引脚。当两个输入引脚电平不一致时输出引脚输出高电平,否则输出低电平。
class XnorGate extends Component { XnorGate(String name) { super(name); } @Override int analysis(List<Integer> list) { return 1 - (list.get(0) ^ list.get(1)); } }
XorGate类:继承Component,包含两个输入引脚和一个输出引脚。当两个输入引脚电平一致时输出引脚输出高电平,否则输出低电平。
class XorGate extends Component { XorGate(String name) { super(name); } @Override int analysis(List<Integer> list) { return list.get(0) ^ list.get(1); } }
对代码进行分析,有:

可以看到,最大圈复杂度为 11,已超过建议的警戒线(10),说明存在个别方法逻辑偏复杂,有潜在出 bug 的风险,建议优先重构。平均每个方法有 6.13 条语句,方法体短小精悍,符合单一职责原则,属于优秀范围。由于是在PTA中编写,所以在这里我只设立了一个大类Main。
本次我的程序的类图如下:

第二次作业
目的:本次作业要求实现的新增四个元件,并能增加对元件的解析。
在此处我仅展示新增的四个类
TriGate类:继承Component,包含一个输入引脚、一个输入控制引脚、一个输出引脚。当控制引脚为高电平时,三态门输入输出之间导通,输出电平等于输入电平;当控制引脚为低电平时,三态门输入输出之间呈现高阻态(类似开关断开),输出为无效状态。
class TriGate extends Component { TriGate(String n){super(n);} @Override void calc(List<Integer> list){ if(list.size()<2){ outVal=null; return; } int ctrl=list.get(0); int dat=list.get(1); if(ctrl==1){ outVal=dat; }else{ outVal=null; } } @Override void printOut(){ if(outVal==null)return; System.out.println(name+"-2:"+outVal); } }
DecoderGate类:继承Component,译码器的作用是将输入的编码转换为一路有效信号。一个译码器包含两个或多个输入引脚(如图中的A2\A1\A0)、三个控制引脚(如图中的S3\S2\S1)、4个或多个输出引脚(如图中的Y7~Y0)。根据输入输出的数量有2-4线译码器、3-8线译码器等,当控制引脚当S1 =1,S2 +S3 =0时,译码器正常工作,输出引脚只有一个输出信号0,其余输出为1;哪个引脚输出0由输入引脚的编码决定。
class DecoderGate extends Component { int inCnt; DecoderGate(String n){ super(n); int l= n.indexOf('('); int r= n.indexOf(')'); inCnt=Integer.parseInt(n.substring(l+1,r)); } @Override void calc(List<Integer> list){ int s1=list.get(0); int s2=list.get(1); int s3=list.get(2); if(s1==1 && s2==0 && s3==0){ int code=0; for(int i=3;i<list.size();i++){ code=code*2+list.get(i); } outVal=code; }else{ outVal=null; } } @Override void printOut(){ if(outVal==null)return; System.out.println(name+"1:"+outVal); } }
MuxGate类:继承Component,数据选择器的作用是从多路输入信号中选择一个,并将其信号直接送往唯一的输出端,选择哪一路输入信号由控制端决定。
class MuxGate extends Component { int ctrlNum; int dataNum; List<Integer> dataBuf; MuxGate(String n){ super(n); int l=n.indexOf('('); int r=n.indexOf(')'); ctrlNum=Integer.parseInt(n.substring(l+1,r)); dataNum=(int)Math.pow(2,ctrlNum); } @Override void calc(List<Integer> list){ if(list.size()<dataNum+ctrlNum){ outVal=null; return; } dataBuf=new ArrayList<>(); for(int i=0;i<dataNum;i++){ dataBuf.add(list.get(i)); } int idx=0; for(int i=dataNum;i<list.size();i++){ idx=idx*2+list.get(i); } outVal=idx; } @Override void printOut(){ if(outVal==null) return; String resStr=""; for(int i=0;i<dataNum;i++){ if(i==outVal) resStr=resStr+dataBuf.get(i); else resStr=resStr+"-"; } System.out.println(name+resStr+"3:"+dataBuf.get(outVal)); } }
DemuxGate类:继承Component,数据分配器的作用与数据选择器正好相反,是将唯一的一路输入信号输出到多路输出引脚的其中之一,选择哪一路输出引脚输出由控制端决定。
class DemuxGate extends Component { int ctrlNum; int outNum; int selectPos; int dataVal; DemuxGate(String n){ super(n); int l=n.indexOf('('); int r=n.indexOf(')'); ctrlNum=Integer.parseInt(n.substring(l+1,r)); outNum=(int)Math.pow(2,ctrlNum); } @Override void calc(List<Integer> list){ if(list.size() < 1 + ctrlNum){ outVal=null; return; } dataVal = list.get(0); int pos=0; for(int i=1;i<list.size();i++){ pos = pos*2 + list.get(i); } selectPos = pos; outVal=pos; } @Override void printOut(){ if(outVal==null) return; String resStr=""; for(int i=0;i<outNum;i++){ if(i == selectPos){ resStr = resStr + dataVal; }else{ resStr = resStr + "-"; } } System.out.println(name+"1:"+resStr); } }
对代码进行分析,有:

可以看到,最大圈复杂度为 12,略高于建议的警戒线(10),说明存在个别方法逻辑偏复杂,存在一定的出 bug 风险,建议对这些高复杂度方法进行拆分优化。平均每个方法有 5.85 条语句,远低于常见阈值(20),方法粒度控制得非常精细,可读性和可维护性良好。
对应类图为:

第三次作业
目的:新增子电路概念和异常输入处理。
SubCircuit类:
static class SubCircuit { String id; List<String> connLines = new ArrayList<>(); Map<String, Integer> portVal = new HashMap<>(); Map<String, Gate> gates = new HashMap<>(); }
parseSubCircuit类:
static int parseSubCircuit(List<String> data, int start) { String head = data.get(start++); String sid = head.substring(0, head.length() - 1); SubCircuit sub = new SubCircuit(); sub.id = sid; while (start < data.size()) { String line = data.get(start++).trim(); if ("endc".equals(line)) break; if (line.startsWith("[")) { sub.connLines.add(line); } } subList.add(sub); return start; }
对代码进行分析,有:

可以看到,最大圈复杂度达到 24,远高于建议的警戒线(10-20),说明代码中至少有一个方法逻辑非常复杂,存在较高的出 bug 风险,建议立即对该方法进行拆分重构。平均每个方法有 19.70 条语句,接近常见的 20 条警戒值,整体方法粒度偏大,建议进一步拆分以提升可读性和可维护性。
类图如下:

对比三次作业的类图来看,一次比一次复杂,对于我的考验也一次比一次大,我深刻的意识到离开了已给出类图的我根本无法进行编程,这也提醒了我要不断进步,根据已给出的类图进行编程并不是自己的能力,而是当能自己设计类并实现才是自己有能力的体现。
踩坑心得
由于第一次作业时设计的类存在严重缺陷,所以在后续的作业里完成的举步维艰,所以说基础是最重要的,如果我第一次作业能花费更多的心思设计好类,后续的作业就能轻松一点,应该来讲,这是我做的6次作业中踩的最大的坑了。
改进建议
我认为第三次作业的代码实在是有点难看,从第三次作业我的代码的类图就可以看出来,我应该把第三次的代码修改一下,分出类来便于代码维护,同时也须知第一次作业的类设计 基础一定一定要设计好,否则接下来的迭代将会很痛苦。
总结
以后要认真对待每一次作业,不能因为第一次题给出的要求简单就不花心思,更相反,第一次作业往往是最重要的一次,这次踩的坑我将铭记于心,端正学习态度。

浙公网安备 33010602011771号