面向对象程序设计作业集4~6的总结性Blog
写在前面
OOP作业集,以航空器配载与货运管理系统程序为中心,不断增加新的功能,我学到了很多:
更加熟悉了Java基本语法;
初步建立起了将继承,多态,接口等用于面向对象的设计思路;
提升阅读复杂项目需求的理解能力;
认识到了代码可拓展性的重要;
学会了使用Idea等IDE便利代码编辑。
学会了构建自动化测试工具。
对本人而言,三次的题目集的题量整体偏难,难度呈上升趋势,光是读懂题目就很需要时间。
Complexity Metrics(复杂度分析)
因为下面要用到复杂度分析,所以先在此给出一些相关概念。
我们需要使用的主要是方法和类的复杂度分析。
方法的复杂度分析主要基于循环复杂度的计算。循环复杂度是一种表示程序复杂度的软件度量,由程序流程图中的“基础路径”数量得来。
- ev(G):即Essentail Complexity,用来表示一个方法的结构化程度,范围在[1,v(G)]之间,值越大则程序的结构越“病态”,其计算过程和图的“缩点”有关。
- iv(G):即Design Complexity,用来表示一个方法和他所调用的其他方法的紧密程度,范围也在[1,v(G)]之间,值越大联系越紧密。
- v(G):即循环复杂度,可以理解为穷尽程序流程每一条路径所需要的试验次数。
对于类,有OCavg和WMC两个项目,分别代表类的方法的平均循环复杂度和总循环复杂度。
下面我将从程序结构,公测、互测以及bug分析几个方面来总结我第一单元的三次作业。
【作业背景】
数字电路是一种处理离散信号的电子电路。与处理连续变化信号(如声音、温度)的模拟电路不同,数字电路只识别和运算两种基本状态:高电平(通常表示为“1”) 和 低电平(通常表示为“0”)。这正好与二进制数制系统相对应,使得数字电路成为所有计算机和数字系统的物理实现基础。
请编程实现数字电路模拟程序,
V2:在V1基础上新添了译码器,数据选择器,数据分配器三个元件,并对输入输出的格式做了调整。
V3:在V1(不是V2)基础上增加的子电路的设计,以及异常输入报错的机制。
OOP作业集04(V1)
【题目描述】
数字电路是一种处理离散信号的电子电路。与处理连续变化信号(如声音、温度)的模拟电路不同,数字电路只识别和运算两种基本状态:高电平(通常表示为“1”) 和 低电平(通常表示为“0”)。这正好与二进制数制系统相对应,使得数字电路成为所有计算机和数字系统的物理实现基础。
请编程实现数字电路模拟程序,
1、电路元件
电路中包含与门、或门、非门、异或门、同或门五种元件。元件特征如下:
与门:
包含两个或多个输入引脚和一个输出引脚。所有输入引脚必须都是高电平,输出引脚才是高电平,只要有一个输入引脚为低电平,输出引脚输出低电平。
或门:

包含两个或多个输入引脚和一个输出引脚。所有输入引脚必须都是低电平,输出引脚才是低电平,只要有一个输入引脚为高电平,输出引脚输出高电平。
非门:

包含一个输入引脚和一个输出引脚。输出引脚的电平与输入引脚的电平相反,如输入为低电平,输出则为高电平。
异或门:

包含两个输入引脚和一个输出引脚。当两个输入引脚电平不一致时输出引脚输出高电平,否则输出低电平。
同或门:

包含两个输入引脚和一个输出引脚。当两个输入引脚电平一致时输出引脚输出高电平,否则输出低电平。
2、程序输入
1)元件信息:
用A、O、N、X、Y 分别用作与门、或门、非门、异或门、同或门五种元件的元件标识符。
电路中的每个与门、或门用“标识符(输入引脚数)+编号”作为其元件名。
例如:A(8)1表示一个8输入引脚的与门,O(4)2代表一个4输入引脚的或门。
电路中的每个非门、异或门、同或门用“标识符+编号”作为其元件名。
例如:X8表示一个异或门,Y4代表一个同或门,N1代表一个非门。
约束条件:
不同元件的编号可以相同,如X4、Y4。
同一电路中同种元件的编号不可重复,可以不连续
2)引脚信息:
引脚信息由“元件名-引脚号”构成,。
例如:A(8)1-2代表与门A(8)1的2号引脚。
3)电路的输入信息:
电路的输入格式:
INPUT:英文空格+输入1+”-”+输入信号1+英文空格+输入2+....+输入n+”-”+输入信号n
例如:
“INPUT: A-0 B-1 C-0”代表整个电路包括3个输入:A、B、C 分别输入0,1,0信号。
4)连接信息
引脚的连接信息格式:
[+输出引脚+英文空格+输入引脚1+。。。。+英文空格+输入引脚+]
例如:
[A A(8)1-1 A(8)1-3 X5-2]
代表信号从引脚A发送给与门A(8)1的1、3两个引脚,以及异或门X5的2号引脚。
[Y8-0 N1-1 O(4)2-3 Y2-1]
代表信号从引脚Y8-0发送给非门N1的1号引脚、或门O(4)2的3号引脚、同或门Y2的1号引脚。
约束条件:
一个输出引脚可以连接多个输入引脚,即将输出引脚的信号传给每一个输入引脚。但一个输入引脚不能连接多个输出引脚。
输出引脚不能短接在一起。
5)输入结束信息
所有输入以end为结束标志,end之后出现的内容忽略不计
3、程序输出
按照与门、或门、非门、异或门、同或门的顺序依次输出所有元件的输出引脚电平。同类元件按编号从小到大的顺序排序。
如果某个元件的引脚没有接有效输入,元件输出无法计算,程序输出结果忽略该元件
4、测试输入默认满足以下条件:
1)每个元件的输入引脚连续编号。假设元件有n个输入引脚,则其编号取值范围为[1,n],且引脚号不重复。
2)本题涉及的五种元件都只有一个输出引脚,输出引脚号默认为0。
5、后续迭代设计
数字电路模拟程序1(本次作业):
基础逻辑门元件构成电路
数字电路模拟程序2:
1、包含多输入输出的组合电路元件如数据选择器;
2、元件引脚类型除输入、输出之外,增加控制引脚,如三态门。
数字电路模拟程序3:
增加带反馈的电路、时序电路元件,如D触发器、JK触发器。
数字电路模拟程序4:
1、增加子电路;
2、增加程序异常输入的检测。
输入样例1:
在这里给出一组输入。例如:
INPUT: A-1 B-1
[A A(2)1-1]
[B A(2)1-2]
[A(2)1-0 OUT]
end
输出样例1:
在这里给出相应的输出。例如:
A(2)1-0:1
输入样例2:
在这里给出一组输入。例如:
INPUT: A-1 B-0
[A X1-1]
[B X1-2]
[X1-0 Y1-1]
[A Y1-2]
[Y1-0 OUT]
end
输出样例2:
在这里给出相应的输出。例如:
X1-0:1
Y1-0:1
输入样例3:
在这里给出一组输入。例如:
INPUT: A-1 B-1 C-1 D-0
[A A(4)1-1]
[B A(4)1-2]
[C A(4)1-3]
[D A(4)1-4]
[A(4)1-0 OUT]
end
输出样例3:
在这里给出相应的输出。例如:
A(4)1-0:0
输入样例4:
在这里给出一组输入。例如:
INPUT: A-1 B-0 C-1
[A A(2)1-1]
[B A(2)1-2]
[A(2)1-0 N1-1]
[N1-0 O(2)1-1]
[C O(2)1-2]
[O(2)1-0 OUT]
end
[O(2)1-0 N2-1]
输出样例4:
在这里给出相应的输出。例如:
A(2)1-0:0
O(2)1-0:1
N1-0:1
输入样例5:
在这里给出一组输入。例如:
INPUT: A-1 B-1 C-0 D-1
[A A(2)1-1 O(2)1-1 X1-1]
[B A(2)1-2]
[C O(2)1-2]
[D X1-2]
[O(2)1-0 A(2)1-0 X1-0 OUT]
end
输出样例5:
在这里给出相应的输出。例如:
A(2)1-0:1
O(2)1-0:1
X1-0:0
输入样例6:
X2输入不全,无法计算输出。例如:
INPUT: A-1 B-1 C-0 D-1
[A A(2)1-1 O(2)1-1 X1-1]
[B A(2)1-2 X2-1]
[C O(2)1-2]
[D X1-2]
[O(2)1-0 A(2)1-0 X1-0 OUT]
end
输出样例6:
输出结果忽略X2。例如:
A(2)1-0:1
O(2)1-0:1
X1-0:0
我提交的源码
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner cin = new Scanner(System.in);
Map<String, Integer> initSignals = new HashMap<String, Integer>();
List<String> wireLines = new ArrayList<String>();
while (cin.hasNextLine()) {
String line = cin.nextLine();
if (line == null) {
break;
}
line = line.trim();
if (line.equals("")) {
continue;
}
if (line.equals("end")) {
break;
}
if (line.startsWith("INPUT:")) {
try {
String content = line.substring(6).trim();
String[] parts = content.split("\\s+");
for (int i = 0; i < parts.length; i++) {
String item = parts[i].trim();
if (item.length() > 0) {
String[] pair = item.split("-");
if (pair.length >= 2) {
initSignals.put(pair[0].trim(), Integer.parseInt(pair[1].trim()));
}
}
}
} catch (Exception e) {
}
} else if (line.startsWith("[")) {
wireLines.add(line);
}
}
Map<String, BaseGate> gMap = new HashMap<String, BaseGate>();
Map<String, List<String>> linkGraph = new HashMap<String, List<String>>();
for (int i = 0; i < wireLines.size(); i++) {
String currentWire = wireLines.get(i);
if (currentWire.length() < 3) {
continue;
}
String core = currentWire.substring(1, currentWire.length() - 1).trim();
String[] nodes = core.split("\\s+");
if (nodes.length < 2) {
continue;
}
String headNode = nodes[0].trim();
for (int j = 1; j < nodes.length; j++) {
String nextNode = nodes[j].trim();
if (nextNode.equals("OUT") || nextNode.equals("")) {
continue;
}
if (!linkGraph.containsKey(headNode)) {
linkGraph.put(headNode, new ArrayList<String>());
}
linkGraph.get(headNode).add(nextNode);
int pos = nextNode.lastIndexOf("-");
if (pos != -1) {
String nameStr = nextNode.substring(0, pos);
if (!gMap.containsKey(nameStr)) {
BaseGate bg = GateHelper.create(nameStr);
if (bg != null) {
gMap.put(nameStr, bg);
}
}
}
}
int srcPos = headNode.lastIndexOf("-");
if (srcPos != -1) {
String srcName = headNode.substring(0, srcPos);
if (!gMap.containsKey(srcName)) {
BaseGate bg = GateHelper.create(srcName);
if (bg != null) {
gMap.put(srcName, bg);
}
}
}
}
Map<String, Integer> runValues = new HashMap<String, Integer>();
Queue<String> runQueue = new LinkedList<String>();
for (String key : initSignals.keySet()) {
runValues.put(key, initSignals.get(key));
runQueue.add(key);
}
while (runQueue.size() > 0) {
String currentPin = runQueue.poll();
Integer pinSignal = runValues.get(currentPin);
if (pinSignal == null) {
continue;
}
List<String> targets = linkGraph.get(currentPin);
if (targets == null) {
continue;
}
for (int i = 0; i < targets.size(); i++) {
String targetStr = targets.get(i);
int dash = targetStr.lastIndexOf("-");
if (dash == -1) {
continue;
}
String gKey = targetStr.substring(0, dash);
int slot = -1;
try {
slot = Integer.parseInt(targetStr.substring(dash + 1)) - 1;
} catch (Exception e) {
continue;
}
BaseGate currentGate = gMap.get(gKey);
if (currentGate == null || slot < 0 || slot >= currentGate.inputs.length) {
continue;
}
currentGate.inputs[slot] = pinSignal;
boolean readyFlag = true;
for (int k = 0; k < currentGate.inputs.length; k++) {
if (currentGate.inputs[k] == -1) {
readyFlag = false;
break;
}
}
if (readyFlag) {
int outputVal = currentGate.compute();
String outPinName = gKey + "-0";
Integer oldVal = runValues.get(outPinName);
if (oldVal == null || oldVal != outputVal) {
runValues.put(outPinName, outputVal);
runQueue.add(outPinName);
}
}
}
}
List<BaseGate> andList = new ArrayList<BaseGate>();
List<BaseGate> orList = new ArrayList<BaseGate>();
List<BaseGate> notList = new ArrayList<BaseGate>();
List<BaseGate> xorList = new ArrayList<BaseGate>();
List<BaseGate> xnorList = new ArrayList<BaseGate>();
for (BaseGate bg : gMap.values()) {
boolean valid = true;
for (int k = 0; k < bg.inputs.length; k++) {
if (bg.inputs[k] == -1) {
valid = false;
break;
}
}
if (valid) {
if (bg.kind.equals("A")) andList.add(bg);
else if (bg.kind.equals("O")) orList.add(bg);
else if (bg.kind.equals("N")) notList.add(bg);
else if (bg.kind.equals("X")) xorList.add(bg);
else if (bg.kind.equals("Y")) xnorList.add(bg);
}
}
Comparator<BaseGate> idSorter = new Comparator<BaseGate>() {
@Override
public int compare(BaseGate b1, BaseGate b2) {
if (b1.id < b2.id) return -1;
if (b1.id > b2.id) return 1;
return 0;
}
};
andList.sort(idSorter);
orList.sort(idSorter);
notList.sort(idSorter);
xorList.sort(idSorter);
xnorList.sort(idSorter);
for (int i = 0; i < andList.size(); i++) {
System.out.println(andList.get(i).fullName + "-0:" + andList.get(i).getOutputValue());
}
for (int i = 0; i < orList.size(); i++) {
System.out.println(orList.get(i).fullName + "-0:" + orList.get(i).getOutputValue());
}
for (int i = 0; i < notList.size(); i++) {
System.out.println(notList.get(i).fullName + "-0:" + notList.get(i).getOutputValue());
}
for (int i = 0; i < xorList.size(); i++) {
System.out.println(xorList.get(i).fullName + "-0:" + xorList.get(i).getOutputValue());
}
for (int i = 0; i < xnorList.size(); i++) {
System.out.println(xnorList.get(i).fullName + "-0:" + xnorList.get(i).getOutputValue());
}
}
interface CircuitElement {
int getOutputValue();
}
abstract static class BaseGate implements CircuitElement {
String fullName;
String kind;
int size;
int id;
int[] inputs;
abstract int compute();
@Override
public int getOutputValue() {
return this.compute();
}
}
static class AndGate extends BaseGate {
@Override
int compute() {
for (int i = 0; i < inputs.length; i++) {
if (inputs[i] == 0) {
return 0;
}
}
return 1;
}
}
static class OrGate extends BaseGate {
@Override
int compute() {
for (int i = 0; i < inputs.length; i++) {
if (inputs[i] == 1) {
return 1;
}
}
return 0;
}
}
static class NotGate extends BaseGate {
@Override
int compute() {
if (inputs[0] == 1) {
return 0;
} else {
return 1;
}
}
}
static class XorGate extends BaseGate {
@Override
int compute() {
if (inputs[0] != inputs[1]) {
return 1;
} else {
return 0;
}
}
}
static class XnorGate extends BaseGate {
@Override
int compute() {
if (inputs[0] == inputs[1]) {
return 1;
} else {
return 0;
}
}
}
static class GateHelper {
public static BaseGate create(String token) {
try {
if (token.startsWith("A(")) {
int idx1 = token.indexOf("(");
int idx2 = token.indexOf(")");
int pNum = Integer.parseInt(token.substring(idx1 + 1, idx2).trim());
int gId = Integer.parseInt(token.substring(idx2 + 1).trim());
AndGate g = new AndGate();
g.fullName = token;
g.kind = "A";
g.id = gId;
g.size = pNum;
g.inputs = new int[pNum];
Arrays.fill(g.inputs, -1);
return g;
} else if (token.startsWith("O(")) {
int idx1 = token.indexOf("(");
int idx2 = token.indexOf(")");
int pNum = Integer.parseInt(token.substring(idx1 + 1, idx2).trim());
int gId = Integer.parseInt(token.substring(idx2 + 1).trim());
OrGate g = new OrGate();
g.fullName = token;
g.kind = "O";
g.id = gId;
g.size = pNum;
g.inputs = new int[pNum];
Arrays.fill(g.inputs, -1);
return g;
} else if (token.startsWith("N")) {
int gId = Integer.parseInt(token.substring(1).trim());
NotGate g = new NotGate();
g.fullName = token;
g.kind = "N";
g.id = gId;
g.size = 1;
g.inputs = new int[1];
g.inputs[0] = -1;
return g;
} else if (token.startsWith("X")) {
int gId = Integer.parseInt(token.substring(1).trim());
XorGate g = new XorGate();
g.fullName = token;
g.kind = "X";
g.id = gId;
g.size = 2;
g.inputs = new int[2];
g.inputs[0] = -1;
g.inputs[1] = -1;
return g;
} else if (token.startsWith("Y")) {
int gId = Integer.parseInt(token.substring(1).trim());
XnorGate g = new XnorGate();
g.fullName = token;
g.kind = "Y";
g.id = gId;
g.size = 2;
g.inputs = new int[2];
g.inputs[0] = -1;
g.inputs[1] = -1;
return g;
}
} catch (Exception e) {
}
return null;
}
}
}
代码规模
V1的代码规模如下
可以看出本电路仿真项目共 9 个 Java 源码文件,项目总行数 544 行,有效业务代码 321 行,注释 142 行,注释覆盖率 26%,文档说明较为充足。代码拆分粒度清晰,各类逻辑职责划分明确,与门、或门等基础器件类仅十余行,体量轻巧。
项目代码体量集中在 CircuitSimulator 主仿真类,单文件有效代码 186 行,承担核心调度逻辑,是后续可进一步拆分优化的模块;整体空行占比 15%,代码排版整洁,属于标准小型课程项目规模。
类图

复杂度分析

整体项目平均圈复杂度、认知复杂度均为 4,整体代码逻辑负担偏高。逻辑门子类的计算方法复杂度很低,结构干净;复杂度压力全部集中在CircuitSimulator与GateHelper,其中构建电路、信号传播、解析输入、器件创建等方法分支判断多,圈复杂度最高达到 9,存在大量嵌套条件,可读性较差,后续可拆分多职责长方法来降低维护成本.
得分详情

V1比上次的迭代作业明显难了很多,但还能接受。
我的反思
本次作业的核心难点是信号传播。电路中的信号不是一次性计算完成的,而是从输入引脚开始,逐级传递到门电路的输入端口,门电路计算输出后再传递给下一级。
我采用了事件驱动的队列传播方式:
-
将所有初始输入信号放入队列
-
从队列中取出一个信号,找到它连接的所有目标引脚
-
将信号值写入目标门电路的对应输入端口
-
如果该门电路的所有输入都已就绪,计算其输出值
-
如果输出值发生了变化,将输出引脚放入队列,继续传播
这种方式的优点是不需要提前确定计算顺序,信号自然会按照连接关系逐级传播。
【具体问题】
1:INPUT: A-1 B-1 这一行,一开始我用 sc.next() 一个一个读,结果读到 [A 这种带方括号的就乱了。后来改成用 sc.nextLine() 整行读,再用 trim() 和 split() 处理,才稳定下来。
2:门的名字五花八门:A(2)1、O(4)2、N1、X5、Y4。每种名字的解析规则不一样。我一开始在 main 里面写了一大堆 if-else,后来专门弄了个 GateHelper 类来处理这件事,至少 main 干净了。
OOP作业集05(V2)
【题目描述】
与V1相比多了:
三态门:

三态门的作用类似于电路中的开关。包含一个输入引脚、一个输入控制引脚、一个输出引脚。当控制引脚为高电平时,三态门输入输出之间导通,输出电平等于输入电平;当控制引脚为低电平时,三态门输入输出之间呈现高阻态(类似开关断开),输出为无效状态。
译码器:

译码器的作用是讲输入的编码转换为一路有效信号。一个译码器包含两个或多个输入引脚(如图中的A2\A1\A0)、三个控制引脚(如图中的S3\S2\S1)、4个或多个输出引脚(如图中的Y7~Y0)。根据输入输出的数量有2-4线译码器、3-8线译码器等。
当控制引脚当S1 =1,S2 +S3 =0时,译码器正常工作,输出引脚只有一个输出信号0,其余输出为1;哪个引脚输出0由输入引脚的编码决定,例如:图中的3-8线译码器三个输入引脚信号的编码与输出引脚的编码对应,A2\A1\A0输入000时,Y0输出0,其余输出1;A2\A1\A0输入001时,Y1输出0,其余输出1;依次类推。
控制引脚不满足S1 =1,S2 +S3 =0时,译码器处于无效状态,所有输出为无效值。
数据选择器:

数据选择器的作用是从多路输入信号中选择一个,并将其信号直接送往唯一的输出端,选择哪一路输入信号由控制端决定。如图所示控制端有两个则输入端有4个,S1\S0是两个控制端,D3D0是输入端,S1\S0的4种信号组合00、01、10、11分别选择D3D0其中一路输入。如S1S0=00,则Y=D0;S1S0=01,则Y=D1;S1S0=10,则Y=D2;S1S0=11,则Y=D3
根据输入引脚数量的不同有二选一数据选择器(1个控制端)、四选一数据选择器(2个控制端)、八选一数据选择器(3个控制端)等
数据分配器:

数据分配器的作用与数据选择器正好相反,是将唯一的一路输入信号输出到多路输出引脚的其中之一,选择哪一路输出引脚输出由控制端决定。如图所示控制端有两个AB,输出端有4个W0\W1\W2\W3,D是输入端,AB的4种信号组合00、01、10、11分别选择W3~W0其中一路输出,其他三路输出为无效状态。如AB=00,则W0=D;AB=01,则W1=D;AB=10,则W2=D;AB=11,则W3=D。
根据输出引脚数量的不同有二路数据分配器(1个控制端)、四路数据分配器(2个控制端)、八路数据分配器(3个控制端)等。
用A、O、N、X、Y、S 、M、Z、F分别用作
与门、或门、非门、异或门、同或门、
三态门、译码器、数据选择器、数据分配器九种元件的元件标识符。
“电路中的每个与门、或门用“标识符(输入引脚数)+编号”作为其元件名。
例如:A(8)1表示一个8输入引脚的与门,O(4)2代表一个4输入引脚的或门。
电路中的每个非门、异或门、同或门用“标识符+编号”作为其元件名。
例如:X8表示一个异或门,Y4代表一个同或门,N1代表一个非门。
电路中的数据选择器、数据分配器用“标识符(控制引脚数)+编号”作为其元件名。
例如:Z(2)2代表一个四选一数据选择器,F(3)2代表一个8路数据分配器。
译码器用“标识符(输入引脚数)+编号”作为其元件名。
例如:M(3)1表示一个3-8线译码器。”
我提交的源码
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
Map<String, Integer> init = new HashMap<>();
List<String> wires = new ArrayList<>();
while (sc.hasNextLine()) {
String line = sc.nextLine().trim();
if (line.equals("end")) break;
if (line.startsWith("INPUT:")) {
String[] parts = line.substring(6).trim().split("\\s+");
for (String p : parts) {
String[] kv = p.split("-");
init.put(kv[0], Integer.parseInt(kv[1]));
}
} else if (line.startsWith("[")) {
wires.add(line);
}
}
Map<String, Gate> gates = new HashMap<>();
Map<String, List<String>> graph = new HashMap<>();
for (String w : wires) {
String inner = w.substring(1, w.length() - 1).trim();
String[] nodes = inner.split("\\s+");
String src = nodes[0];
for (int i = 1; i < nodes.length; i++) {
String dst = nodes[i];
if (dst.equals("OUT")) continue;
graph.computeIfAbsent(src, k -> new ArrayList<>()).add(dst);
String gname = getGateName(dst);
if (gname != null && !gates.containsKey(gname))
gates.put(gname, GateFactory.create(gname));
}
String gname = getGateName(src);
if (gname != null && !gates.containsKey(gname))
gates.put(gname, GateFactory.create(gname));
}
Map<String, Integer> values = new HashMap<>(init);
Queue<String> q = new LinkedList<>(init.keySet());
while (!q.isEmpty()) {
String cur = q.poll();
if (!values.containsKey(cur)) continue;
int val = values.get(cur);
List<String> targets = graph.get(cur);
if (targets == null) continue;
for (String t : targets) {
int dash = t.lastIndexOf("-");
if (dash == -1) continue;
String gname = t.substring(0, dash);
int pin = Integer.parseInt(t.substring(dash + 1));
Gate g = gates.get(gname);
if (g == null) continue;
g.set(pin, val);
if (g.ready()) {
g.compute();
for (Map.Entry<String, Integer> e : g.outputs().entrySet()) {
String outPin = e.getKey();
int outVal = e.getValue();
if (outVal != -1 && (!values.containsKey(outPin) || values.get(outPin) != outVal)) {
values.put(outPin, outVal);
q.add(outPin);
}
}
}
}
}
List<Gate> and = new ArrayList<>(), or = new ArrayList<>(), not = new ArrayList<>();
List<Gate> xor = new ArrayList<>(), xnor = new ArrayList<>(), tri = new ArrayList<>();
List<Gate> dec = new ArrayList<>(), mux = new ArrayList<>(), demux = new ArrayList<>();
for (Gate g : gates.values()) {
if (!g.valid()) continue;
switch (g.type) {
case "A": and.add(g); break;
case "O": or.add(g); break;
case "N": not.add(g); break;
case "X": xor.add(g); break;
case "Y": xnor.add(g); break;
case "S": tri.add(g); break;
case "M": dec.add(g); break;
case "Z": mux.add(g); break;
case "F": demux.add(g); break;
}
}
Comparator<Gate> c = Comparator.comparingInt(g -> g.id);
and.sort(c); or.sort(c); not.sort(c); xor.sort(c); xnor.sort(c);
tri.sort(c); dec.sort(c); mux.sort(c); demux.sort(c);
for (Gate g : and) System.out.println(g.name + "-0:" + g.getOutput());
for (Gate g : or) System.out.println(g.name + "-0:" + g.getOutput());
for (Gate g : not) System.out.println(g.name + "-0:" + g.getOutput());
for (Gate g : xor) System.out.println(g.name + "-0:" + g.getOutput());
for (Gate g : xnor) System.out.println(g.name + "-0:" + g.getOutput());
for (Gate g : tri) System.out.println(g.name + "-2:" + g.getOutput());
for (Gate g : dec) {
String s = g.getDecoder();
if (s != null) System.out.println(s);
}
for (Gate g : mux) {
if (g.valid()) System.out.println(g.name + "-" + g.getOutPin() + ":" + g.getOutput());
}
for (Gate g : demux) {
String s = g.getDemux();
if (s != null) System.out.println(s);
}
}
static String getGateName(String s) {
int d = s.lastIndexOf("-");
return d == -1 ? null : s.substring(0, d);
}
}
abstract class Gate {
String name; String type; int id;
Map<Integer, Integer> in = new HashMap<>();
Map<String, Integer> out = new HashMap<>();
abstract void compute();
abstract boolean ready();
abstract boolean valid();
void set(int pin, int v) { in.put(pin, v); }
Map<String, Integer> outputs() { return out; }
int getOutput() { return out.isEmpty() ? -1 : out.values().iterator().next(); }
int getOutPin() { return Integer.parseInt(out.keySet().iterator().next().split("-")[1]); }
String getDecoder() { return null; }
String getDemux() { return null; }
}
class AndGate extends Gate {
int n;
AndGate(String s) {
name = s; type = "A";
int l = s.indexOf('('), r = s.indexOf(')');
n = Integer.parseInt(s.substring(l+1, r));
id = Integer.parseInt(s.substring(r+1));
}
void compute() {
int r = 1;
for (int i=1; i<=n; i++) if (in.getOrDefault(i, -1)==0) { r=0; break; }
out.clear(); out.put(name+"-0", r);
}
boolean ready() { for (int i=1; i<=n; i++) if (!in.containsKey(i)) return false; return true; }
boolean valid() { return ready(); }
}
class OrGate extends Gate {
int n;
OrGate(String s) {
name = s; type = "O";
int l = s.indexOf('('), r = s.indexOf(')');
n = Integer.parseInt(s.substring(l+1, r));
id = Integer.parseInt(s.substring(r+1));
}
void compute() {
int r = 0;
for (int i=1; i<=n; i++) if (in.getOrDefault(i, -1)==1) { r=1; break; }
out.clear(); out.put(name+"-0", r);
}
boolean ready() { for (int i=1; i<=n; i++) if (!in.containsKey(i)) return false; return true; }
boolean valid() { return ready(); }
}
class NotGate extends Gate {
NotGate(String s) { name = s; type = "N"; id = Integer.parseInt(s.substring(1)); }
void compute() { out.clear(); out.put(name+"-0", in.get(1)==1?0:1); }
boolean ready() { return in.containsKey(1); }
boolean valid() { return ready(); }
}
class XorGate extends Gate {
XorGate(String s) { name = s; type = "X"; id = Integer.parseInt(s.substring(1)); }
void compute() { out.clear(); out.put(name+"-0", in.get(1)!=in.get(2)?1:0); }
boolean ready() { return in.containsKey(1) && in.containsKey(2); }
boolean valid() { return ready(); }
}
class XnorGate extends Gate {
XnorGate(String s) { name = s; type = "Y"; id = Integer.parseInt(s.substring(1)); }
void compute() { out.clear(); out.put(name+"-0", in.get(1)==in.get(2)?1:0); }
boolean ready() { return in.containsKey(1) && in.containsKey(2); }
boolean valid() { return ready(); }
}
class TriGate extends Gate {
TriGate(String s) { name = s; type = "S"; id = Integer.parseInt(s.substring(1)); }
void compute() {
out.clear();
if (in.getOrDefault(0,0)==1) out.put(name+"-2", in.get(1));
}
boolean ready() { return in.containsKey(0) && in.containsKey(1); }
boolean valid() { return ready() && in.getOrDefault(0,0)==1 && out.containsKey(name+"-2"); }
}
class DecoderGate extends Gate {
int bits;
DecoderGate(String s) {
name = s; type = "M";
int l = s.indexOf('('), r = s.indexOf(')');
bits = Integer.parseInt(s.substring(l+1, r));
id = Integer.parseInt(s.substring(r+1));
}
void compute() {
out.clear();
if (!valid()) return;
int code = 0;
for (int i=0; i<bits; i++) if (in.getOrDefault(3+i,0)==1) code |= (1<<i);
int base = 3+bits;
for (int i=0; i<(1<<bits); i++) out.put(name+"-"+(base+i), i==code?0:1);
}
boolean ready() { for (int i=0; i<3+bits; i++) if (!in.containsKey(i)) return false; return true; }
boolean valid() { return ready() && in.getOrDefault(0,0)==1 && in.getOrDefault(1,0)==0 && in.getOrDefault(2,0)==0; }
String getDecoder() {
if (!valid()) return null;
int code = 0;
for (int i=0; i<bits; i++) if (in.getOrDefault(3+i,0)==1) code |= (1<<i);
return name+":"+code;
}
}
class MuxGate extends Gate {
int selBits, dataPins;
MuxGate(String s) {
name = s; type = "Z";
int l = s.indexOf('('), r = s.indexOf(')');
selBits = Integer.parseInt(s.substring(l+1, r));
dataPins = 1 << selBits;
id = Integer.parseInt(s.substring(r+1));
}
void compute() {
out.clear();
int sel = 0;
for (int i = 0; i < selBits; i++) if (in.getOrDefault(i,0)==1) sel |= (1 << i);
int outVal = in.getOrDefault(selBits + sel, -1);
if (outVal != -1) {
out.put(name + "-" + (selBits + dataPins), outVal);
}
}
boolean ready() { for (int i=0; i<selBits+dataPins; i++) if (!in.containsKey(i)) return false; return true; }
boolean valid() { return ready() && out.containsKey(name + "-" + (selBits + dataPins)); }
int getOutPin() { return selBits + dataPins; }
}
class DemuxGate extends Gate {
int selBits, outPins;
DemuxGate(String s) {
name = s; type = "F";
int l = s.indexOf('('), r = s.indexOf(')');
selBits = Integer.parseInt(s.substring(l+1, r));
outPins = 1 << selBits;
id = Integer.parseInt(s.substring(r+1));
}
void compute() {
out.clear();
int sel = 0;
for (int i = 0; i < selBits; i++) if (in.getOrDefault(i,0)==1) sel |= (1 << i);
int data = in.getOrDefault(selBits, -1);
int base = selBits + 1;
for (int i = 0; i < outPins; i++) {
out.put(name + "-" + (base + i), i == sel ? data : -1);
}
}
boolean ready() { for (int i=0; i<=selBits; i++) if (!in.containsKey(i)) return false; return true; }
boolean valid() { return ready(); }
String getDemux() {
if (!ready()) return null;
StringBuilder sb = new StringBuilder(name + ":");
int base = selBits + 1;
for (int i = 0; i < outPins; i++) {
Integer v = out.get(name + "-" + (base + i));
sb.append(v == null || v == -1 ? "-" : v);
}
return sb.toString();
}
}
class GateFactory {
static Gate create(String s) {
if (s.startsWith("A(")) return new AndGate(s);
if (s.startsWith("O(")) return new OrGate(s);
if (s.startsWith("N")) return new NotGate(s);
if (s.startsWith("X")) return new XorGate(s);
if (s.startsWith("Y")) return new XnorGate(s);
if (s.startsWith("S")) return new TriGate(s);
if (s.startsWith("M(")) return new DecoderGate(s);
if (s.startsWith("Z(")) return new MuxGate(s);
if (s.startsWith("F(")) return new DemuxGate(s);
return null;
}
}
代码规模

本版本共 12 个 Java 源文件,总代码 798 行,有效业务代码 530 行,注释 148 行,注释覆盖率 19%,代码留白占比 15%,排版规整。各类门器件子类体量小巧,逻辑集中在 CircuitSimulator 主仿真类(有效代码 175 行);GateFactory 注释占比过半,文档完善,整体属于小型数字电路仿真项目。
类图

复杂度分析


整体水平:项目平均圈复杂度仅 2.55、平均认知复杂度 1.94,整体代码分支嵌套少,基础门器件类的方法复杂度普遍低于 4,结构清晰易维护。
问题集中点:两处高复杂度方法存在明显优化空间,CircuitSimulator.printSimResult圈复杂度 12、GateFactory.create圈复杂度 12,二者存在大量 if 分支判断,阅读与单元测试成本高;CircuitSimulator.propagateSignal认知复杂度 14,嵌套逻辑深,理解难度偏大。
分层表现:基础逻辑门(与 / 或 / 异或 / 多路选择器等)的计算、状态校验方法复杂度极低,职责单一;全部复杂度压力集中在电路仿真主类与器件工厂方法,后续可通过拆分分支逻辑、抽取独立子方法完成优化。
得分详情

失分点:

(我说出题老师真得公开测试点,就算因为资源匮乏公开不了也不要拿“以后客户会给你测试点吗”来压人啊QAQ)
我的反思
V2 版本我新增了四种元件,其中译码器是最复杂的。我在实现时犯了一个低级错误:把译码器的输出电平写反了。题目要求选中通道输出 0,其余输出 1,我却写成了选中输出 1,其余输出 0。这个错误导致所有用到译码器的测试点全部失败,包括基础元件测试和复杂电路测试。
这个教训告诉我,对于复杂元件的实现,不能只凭记忆写代码,应该在写完后对照题目要求逐条验证。如果我当时画一张表格,把每个引脚的功能和对应的输出值列出来,就不容易写反。
另外,数据选择器和数据分配器的引脚编号计算也需要格外小心,这类数字逻辑题的得分往往取决于这些细节是否准确。
OOP作业集06(V3)
【题目描述】
在V1基础上新增:
子电路信息(本题在数字电路模拟程序-1基础上新增的内容)
可以将一部分电路设定为一个子电路,子电路可以在主电路中被引用。子电路的输入按顺序包括以下几部分:
子电路起始信息:C+子电路编号+:
子电路输入信息:
INPUT:输入1+英文空格+输入2+英文空格+....+英文空格+输入n,
子电路输出信息:
OUT:输出1+英文空格+输出2+英文空格+....+英文空格+输出n,
子电路连接信息:(格式与主电路输入信息相同),子电路的元件编号与主电路中的编号可能相同。
子电路结束标志:endc
异常输入信息(本题在数字电路模拟程序-1基础上新增的内容)
当出现各类异常输入情况时,电路应输出相应提示,异常情况包括:
1.一个连接信息中包含两个或多个输入,输出:ERROR:“连接信息”+“英文空格”+include more than one input
例如:
INPUT: A-1 B-1
[A B A2-1]
应输出:
ERROR: [A B A2-1] include more than one input
2.一个连接信息中没有输入信息,输出:ERROR:“连接信息”+“英文空格”+include none input
例如:
INPUT: A-1 B-1
[C1-1 C2-1]
应输出:
ERROR: [C1-1 C2-1] include none input
注意:C1-1 C2-1引脚是元件的输入引脚,但作为连接信息,元件的输入引脚属于连接系统的输出,所以是none input
3.一个连接信息中没有输出,输出:ERROR:“连接信息”+“英文空格”+include none output
例如:
INPUT: A-1 B-1
[A]
应输出:ERROR: [A] include none output
4.一个连接信息中输入输出写反,输出:ERROR:“连接信息”+“英文空格”+input and output sequence error
例如:
INPUT: A-1 B-1
[A2-1 A]
应输出:ERROR: [A2-1 A] input and output sequence error
5.一个输入引脚接受中来自多个不同输出的信号,输出:ERROR:“输入引脚”+“英文空格”+input signal conflict
例如:
INPUT: A-1 B-1
[A A2-1]
[B A2-1]
应输出:ERROR: A2-1 input signal conflict
注:如果一条输入出现了多种异常,按以上异常先后顺序为优先级,顺序靠前者优先级越高,最后输出优先级最高的异常输出。
当电路输入信息中多条输入都包含异常,只处理排在最前面的异常信息。
我提交的源码
import java.util.*;
abstract class Gate {
String name;
String type;
int id;
Map<Integer, Integer> inputPins = new HashMap<>();
abstract int getInputCount();
abstract boolean ready();
abstract int compute();
void setInput(int pin, int value) {
inputPins.put(pin, value);
}
}
class AndGate extends Gate {
int inCount;
AndGate(String s) {
name = s; type = "A";
int l = s.indexOf('('), r = s.indexOf(')');
inCount = Integer.parseInt(s.substring(l + 1, r));
id = Integer.parseInt(s.substring(r + 1));
}
int getInputCount() { return inCount; }
boolean ready() {
for (int i = 1; i <= inCount; i++)
if (!inputPins.containsKey(i)) return false;
return true;
}
int compute() {
for (int i = 1; i <= inCount; i++)
if (inputPins.get(i) == 0) return 0;
return 1;
}
}
class OrGate extends Gate {
int inCount;
OrGate(String s) {
name = s; type = "O";
int l = s.indexOf('('), r = s.indexOf(')');
inCount = Integer.parseInt(s.substring(l + 1, r));
id = Integer.parseInt(s.substring(r + 1));
}
int getInputCount() { return inCount; }
boolean ready() {
for (int i = 1; i <= inCount; i++)
if (!inputPins.containsKey(i)) return false;
return true;
}
int compute() {
for (int i = 1; i <= inCount; i++)
if (inputPins.get(i) == 1) return 1;
return 0;
}
}
class NotGate extends Gate {
NotGate(String s) { name = s; type = "N"; id = Integer.parseInt(s.substring(1)); }
int getInputCount() { return 1; }
boolean ready() { return inputPins.containsKey(1); }
int compute() { return inputPins.get(1) == 0 ? 1 : 0; }
}
class XorGate extends Gate {
XorGate(String s) { name = s; type = "X"; id = Integer.parseInt(s.substring(1)); }
int getInputCount() { return 2; }
boolean ready() { return inputPins.containsKey(1) && inputPins.containsKey(2); }
int compute() { return inputPins.get(1) != inputPins.get(2) ? 1 : 0; }
}
class XnorGate extends Gate {
XnorGate(String s) { name = s; type = "Y"; id = Integer.parseInt(s.substring(1)); }
int getInputCount() { return 2; }
boolean ready() { return inputPins.containsKey(1) && inputPins.containsKey(2); }
int compute() { return inputPins.get(1).equals(inputPins.get(2)) ? 1 : 0; }
}
class SubCircuitDef {
int id;
List<String> inputs = new ArrayList<>();
List<String> outputs = new ArrayList<>();
List<String[]> connections = new ArrayList<>();
}
public class Main {
static Map<Integer, SubCircuitDef> subDefs = new LinkedHashMap<>();
static Map<String, Integer> externalInputs = new LinkedHashMap<>();
static Map<String, List<String>> allConns = new LinkedHashMap<>();
static Map<String, Gate> allGates = new LinkedHashMap<>();
static String errorMsg = null;
static Set<String> assignedInputPins = new HashSet<>();
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
List<String> lines = new ArrayList<>();
while (sc.hasNextLine()) {
String line = sc.nextLine().stripTrailing();
if (line.trim().isEmpty()) continue;
lines.add(line.trim());
}
sc.close();
parseAll(lines);
if (errorMsg != null) {
System.out.print(errorMsg);
return;
}
expandAllSubCircuits();
simulate();
List<Gate> sortedGates = new ArrayList<>(allGates.values());
// FIX: sort by (typeOrder, id, then numeric subcircuit-prefix id, then full name)
// instead of plain string comparison, which breaks for 2+ digit subcircuit ids (e.g. C10 vs C2)
sortedGates.sort((a, b) -> {
int ta = typeOrder(a.type), tb = typeOrder(b.type);
if (ta != tb) return ta - tb;
if (a.id != b.id) return Integer.compare(a.id, b.id);
int pa = subPrefixId(a.name), pb = subPrefixId(b.name);
if (pa != pb) return Integer.compare(pa, pb);
return a.name.compareTo(b.name);
});
StringBuilder sb = new StringBuilder();
for (Gate g : sortedGates) {
if (!g.ready()) continue;
int val = g.compute();
sb.append(g.name).append("-0:").append(val).append("\n");
}
System.out.print(sb.toString());
}
// Returns the numeric subcircuit id if name starts with "C<digits>-", else -1 (main circuit gates sort first)
static int subPrefixId(String name) {
if (name.matches("C\\d+-.*")) {
String numPart = name.substring(1, name.indexOf('-'));
return Integer.parseInt(numPart);
}
return -1;
}
static int typeOrder(String t) {
switch (t) {
case "A": return 0;
case "O": return 1;
case "N": return 2;
case "X": return 3;
case "Y": return 4;
default: return 5;
}
}
static void parseAll(List<String> lines) {
int idx = 0;
while (idx < lines.size()) {
String line = lines.get(idx);
if (line.matches("C\\d+:")) {
int subId = Integer.parseInt(line.substring(1, line.length() - 1));
SubCircuitDef def = new SubCircuitDef();
def.id = subId;
idx++;
while (idx < lines.size()) {
String l = lines.get(idx++);
if (l.equals("endc")) break;
// FIX: also accept fullwidth colon ":" since it appears in the official problem text's own example
if (l.startsWith("INPUT:") || l.startsWith("INPUT:")) {
String rest = l.substring(l.indexOf(':') >= 0 ? l.indexOf(':') + 1
: l.indexOf(':') + 1).trim();
for (String p : rest.split("\\s+")) if (!p.isEmpty()) def.inputs.add(p);
} else if (l.startsWith("OUT:") || l.startsWith("OUT:")) {
String rest = l.substring(l.indexOf(':') >= 0 ? l.indexOf(':') + 1
: l.indexOf(':') + 1).trim();
for (String p : rest.split("\\s+")) if (!p.isEmpty()) def.outputs.add(p);
} else if (l.startsWith("[") && l.endsWith("]")) {
String inner = l.substring(1, l.length() - 1).trim();
String[] toks = inner.split("\\s+");
// FIX: also validate connections INSIDE subcircuit definitions.
// Previously only main-circuit connection lines were checked for errors,
// so malformed connections inside a subcircuit body were silently ignored.
if (errorMsg == null) {
String err = checkErrorInSub(l, toks, def);
if (err != null) { errorMsg = err; return; }
}
def.connections.add(toks);
}
}
subDefs.put(subId, def);
} else {
break;
}
}
while (idx < lines.size()) {
String line = lines.get(idx++);
if (line.equals("end")) break;
if (line.startsWith("INPUT:")) {
String rest = line.substring(6).trim();
for (String p : rest.split("\\s+")) {
int dash = p.lastIndexOf('-');
if (dash > 0) {
String key = p.substring(0, dash);
int val = Integer.parseInt(p.substring(dash + 1));
externalInputs.put(key, val);
}
}
} else if (line.startsWith("[") && line.endsWith("]")) {
String inner = line.substring(1, line.length() - 1).trim();
String[] toks = inner.split("\\s+");
if (errorMsg == null) {
String err = checkError(line, toks);
if (err != null) {
errorMsg = err;
return;
}
}
String src = toks[0];
List<String> dests = new ArrayList<>();
for (int i = 1; i < toks.length; i++) dests.add(toks[i]);
for (String d : dests) {
if (assignedInputPins.contains(d)) {
errorMsg = "ERROR: " + d + " input signal conflict";
return;
}
assignedInputPins.add(d);
}
processMainConnection(src, dests);
}
}
}
static String checkError(String fullLine, String[] toks) {
int outputSideCount = 0;
boolean firstIsOutputSide = false;
for (int i = 0; i < toks.length; i++) {
boolean isOut = isOutputSide(toks[i]);
if (isOut) outputSideCount++;
if (i == 0) firstIsOutputSide = isOut;
}
if (outputSideCount > 1) return "ERROR: " + fullLine + " include more than one input";
if (outputSideCount == 0) return "ERROR: " + fullLine + " include none input";
if (!firstIsOutputSide) return "ERROR: " + fullLine + " input and output sequence error";
if (toks.length == 1) return "ERROR: " + fullLine + " include none output";
return null;
}
static boolean isOutputSide(String pin) {
if (externalInputs.containsKey(pin)) return true;
if (pin.matches(".+-0$")) return true;
if (pin.matches("C\\d+-.+")) {
String[] parts = pin.split("-", 2);
int subId = Integer.parseInt(parts[0].substring(1));
SubCircuitDef def = subDefs.get(subId);
if (def != null && def.outputs.contains(parts[1])) return true;
}
return false;
}
// FIX (new): error checking for connection lines INSIDE a subcircuit definition.
// "Output side" here means: the subcircuit's own INPUT ports (they source the signal
// from inside the subcircuit's perspective) or a gate output pin ("-0").
static String checkErrorInSub(String fullLine, String[] toks, SubCircuitDef def) {
int outputSideCount = 0;
boolean firstIsOutputSide = false;
for (int i = 0; i < toks.length; i++) {
boolean isOut = isOutputSideInSub(toks[i], def);
if (isOut) outputSideCount++;
if (i == 0) firstIsOutputSide = isOut;
}
if (outputSideCount > 1) return "ERROR: " + fullLine + " include more than one input";
if (outputSideCount == 0) return "ERROR: " + fullLine + " include none input";
if (!firstIsOutputSide) return "ERROR: " + fullLine + " input and output sequence error";
if (toks.length == 1) return "ERROR: " + fullLine + " include none output";
return null;
}
static boolean isOutputSideInSub(String pin, SubCircuitDef def) {
if (def.inputs.contains(pin)) return true;
if (pin.matches(".+-0$")) return true;
return false;
}
static void processMainConnection(String src, List<String> dests) {
List<String> expandedDests = new ArrayList<>();
for (String d : dests) {
if (isSubCircuitInputPort(d)) {
expandSubCircuitInput(d, expandedDests);
} else {
expandedDests.add(d);
registerGate(d);
}
}
if (isSubCircuitOutputPort(src)) {
String resolved = resolveSubCircuitOutput(src);
if (resolved != null) addConn(resolved, expandedDests);
} else {
registerGate(src);
addConn(src, expandedDests);
}
}
static boolean isSubCircuitInputPort(String pin) {
if (!pin.matches("C\\d+-.+")) return false;
String[] parts = pin.split("-", 2);
int subId = Integer.parseInt(parts[0].substring(1));
SubCircuitDef def = subDefs.get(subId);
return def != null && def.inputs.contains(parts[1]);
}
static boolean isSubCircuitOutputPort(String pin) {
if (!pin.matches("C\\d+-.+")) return false;
String[] parts = pin.split("-", 2);
int subId = Integer.parseInt(parts[0].substring(1));
SubCircuitDef def = subDefs.get(subId);
return def != null && def.outputs.contains(parts[1]);
}
static void expandSubCircuitInput(String port, List<String> result) {
String[] parts = port.split("-", 2);
String prefix = parts[0];
String portName = parts[1];
int subId = Integer.parseInt(prefix.substring(1));
SubCircuitDef def = subDefs.get(subId);
if (def == null) return;
for (String[] conn : def.connections) {
if (conn.length > 0 && conn[0].equals(portName)) {
for (int i = 1; i < conn.length; i++) {
String d = conn[i];
String flat = prefix + "-" + d;
result.add(flat);
registerGate(flat);
}
}
}
}
static String resolveSubCircuitOutput(String port) {
String[] parts = port.split("-", 2);
String prefix = parts[0];
String portName = parts[1];
int subId = Integer.parseInt(prefix.substring(1));
SubCircuitDef def = subDefs.get(subId);
if (def == null) return null;
for (String[] conn : def.connections) {
for (int i = 1; i < conn.length; i++) {
if (conn[i].equals(portName)) {
String flatSrc = prefix + "-" + conn[0];
registerGate(flatSrc);
return flatSrc;
}
}
}
return null;
}
static void registerGate(String pin) {
String gateName = extractGateName(pin);
if (gateName == null || externalInputs.containsKey(gateName) || allGates.containsKey(gateName)) return;
Gate g = createGate(gateName);
if (g != null) allGates.put(gateName, g);
}
static String extractGateName(String pin) {
int dash = pin.lastIndexOf('-');
if (dash < 0) return null;
String suffix = pin.substring(dash + 1);
if (!suffix.matches("\\d+")) return null;
String gname = pin.substring(0, dash);
if (externalInputs.containsKey(gname)) return null;
return gname;
}
static Gate createGate(String name) {
String local = name;
if (name.matches("C\\d+-.+")) {
local = name.replaceFirst("C\\d+-", "");
}
Gate g = null;
if (local.startsWith("A(")) g = new AndGate(local);
else if (local.startsWith("O(")) g = new OrGate(local);
else if (local.startsWith("N")) g = new NotGate(local);
else if (local.startsWith("X")) g = new XorGate(local);
else if (local.startsWith("Y")) g = new XnorGate(local);
if (g != null) g.name = name;
return g;
}
static void addConn(String src, List<String> dests) {
List<String> list = allConns.computeIfAbsent(src, k -> new ArrayList<>());
for (String d : dests) {
if (!list.contains(d)) list.add(d);
}
}
static void expandAllSubCircuits() {
Set<String> prefixes = new HashSet<>();
for (String k : allConns.keySet()) if (k.matches("C\\d+-.+")) prefixes.add(k.split("-")[0]);
for (List<String> vs : allConns.values()) for (String v : vs) if (v.matches("C\\d+-.+")) prefixes.add(v.split("-")[0]);
for (String prefix : prefixes) {
int subId = Integer.parseInt(prefix.substring(1));
SubCircuitDef def = subDefs.get(subId);
if (def == null) continue;
for (String[] conn : def.connections) {
String src = conn[0];
if (def.inputs.contains(src)) continue;
String flatSrc = prefix + "-" + src;
registerGate(flatSrc);
List<String> dests = new ArrayList<>();
for (int i = 1; i < conn.length; i++) {
String d = conn[i];
if (def.outputs.contains(d)) continue;
String flatD = prefix + "-" + d;
registerGate(flatD);
dests.add(flatD);
}
if (!dests.isEmpty()) addConn(flatSrc, dests);
}
}
}
static void simulate() {
Map<String, Integer> values = new LinkedHashMap<>();
for (Map.Entry<String, Integer> e : externalInputs.entrySet()) {
values.put(e.getKey(), e.getValue());
}
Queue<String> queue = new LinkedList<>(values.keySet());
Set<String> inQueue = new HashSet<>(values.keySet());
while (!queue.isEmpty()) {
String src = queue.poll();
inQueue.remove(src);
Integer val = values.get(src);
if (val == null) continue;
List<String> dests = allConns.get(src);
if (dests == null) continue;
for (String dest : dests) {
Integer old = values.put(dest, val);
if (old != null && old.equals(val)) continue;
String gname = extractGateName(dest);
if (gname != null) {
Gate g = allGates.get(gname);
if (g != null) {
try {
int pin = Integer.parseInt(dest.substring(dest.lastIndexOf('-') + 1));
g.setInput(pin, val);
if (g.ready()) {
int out = g.compute();
String outKey = g.name + "-0";
Integer oldOut = values.put(outKey, out);
if (oldOut == null || oldOut != out) {
if (!inQueue.contains(outKey)) {
queue.add(outKey);
inQueue.add(outKey);
}
}
}
} catch (Exception ignored) {}
}
} else {
if (!inQueue.contains(dest)) {
queue.add(dest);
inQueue.add(dest);
}
}
}
}
}
}
代码规模

共 13 个 Java 源文件,总行数 988 行,有效业务代码 618 行,注释 222 行,整体注释覆盖率 22%,代码空行占比 15%,排版规范整洁。项目体量集中在电路解析模块CircuitParser.java(有效代码 191 行),承担核心文本解析逻辑;基础逻辑门类行数普遍 30 行左右,拆分粒度小、职责单一;校验、信号传播、子电路拓展等辅助类体量适中,仅数据定义类SubCircuitDef.java注释占比超五成,文档说明充足,整体属于中等规模课程仿真项目。
类图

复杂度分析

项目平均圈复杂度 3.39、平均认知复杂度 4.05,整体逻辑负担偏高,基础逻辑门类方法复杂度普遍偏低,问题高度集中在核心业务方法:
最严重痛点:CircuitParser.parse认知复杂度 67、圈复杂度 24,SignalPropagator.propagate认知复杂度 23、圈复杂度 10,嵌套与分支极多,可读性、单元测试难度极大;
次要高复杂度方法:GateFactory 创建器件、错误校验、子电路展开等方法圈复杂度达到 8~10,存在大量条件判断;
分层表现:基础门(与 / 或 / 非 / 异或)的计算、状态查询方法复杂度大多在 1~3,职责单一干净;全部复杂逻辑集中在解析、信号传播、子电路扩展三大核心模块,后续可抽取独立子方法拆分长流程、简化分支判断完成优化。
得分详情

失分点:


我的反思
V3 最大的教训是:子电路不是一个简单的“把内部连线复制过来”就能解决的问题。它本质上是一个新的抽象层次,需要把子电路当作一个独立的计算单元来处理,而不是把它拆散后融入主电路。
我选择“展开”的方式,表面上简化了问题,但实际上引入了更多复杂的问题:信号源的映射、端口名的解析、输出格式的生成等。如果采用组合模式,把子电路封装成一个独立的 Gate 类型,每个子电路有自己的 compute() 和 ready() 方法,可能反而更清晰。
这也提醒我:在遇到复杂问题时,不要急于找一个“看起来简单”的解决方案,而应该先理解问题的本质,再选择合适的设计模式。V4 还要增加反馈电路和时序元件,如果继续用这种“展开”的方式,代码会越来越难以维护。
总结
三轮迭代的核心教训是:功能正确只是及格线,选择正确的设计策略才是决定代码能否持续扩展的关键。V1我靠堆代码勉强跑通,但缺乏分层设计;V2新增组合逻辑元件时,译码器输出电平取反这一处细节错误导致基础测试挂掉;到了V3,子电路的引入让我的“展开式”设计策略彻底暴露问题——我试图把子电路内部连线复制到全局连接图来简化逻辑,但这种做法反而把信号映射、端口解析、输出格式生成全部搅在一起,最终子电路相关测试点全军覆没。回头来看,如果V3采用组合模式,把子电路封装成一个特殊的Gate类型,让它有自己的compute()和ready()方法,而不是强行展开,代码会更清晰。接下来的V4要处理反馈电路和时序元件,我需要在动手之前先画清楚数据流图,并且针对每个新功能写独立的本地测试用例,而不是依赖PTA的测试点告诉我哪里错了。
浙公网安备 33010602011771号