4-6次作业集总结
前言
本次迭代式数字电路模拟编程作业包含三道递进式题目,分别为:基础门电路模拟、多引脚组合元件扩展、子电路+异常输入校验综合实现。三道题目遵循循序渐进的迭代开发逻辑,题量逐层递增、实现难度阶梯式上升,完整覆盖 Java 面向对象基础、组合模式设计、电路信号传播模拟、输入语法校验、异常分支处理等核心开发知识点。
三道作业并非独立功能开发,而是同一项目的三次迭代重构,所有新增功能均需向下兼容旧版本输入用例,对代码分层解耦、架构扩展性、程序健壮性均提出了硬性要求。
一、作业迭代维度拆分
1.1 作业4:基础门电路(入门层)
仅实现与、或、非、异或、同或5种基础逻辑门;输入仅支持全局输入信号、单层级连接语句;无异常校验、无复合子电路。
核心工作量:字符串解析、信号队列传播、门电路布尔运算逻辑。
工程指标:代码总行数约 378行,分支复杂度均值 4.36。
1.2 作业5:多引脚组合元件扩展(进阶层)
在作业1基础上新增三态门、译码器、数据选择器、数据分配器4类复合元件,引入控制引脚、多路输出引脚两大核心新特性,差异化各类元件输出打印规则,新增多层迭代信号刷新机制。
工程指标:代码总行数约 424行,平均圈复杂度提升至 5.03,方法分支数量显著上涨。
1.3 作业6:子电路+异常输入校验(高阶层)
基于作业2抽象架构全面重构,采用组合模式区分叶子门电路与复合容器子电路;新增子电路解析、引脚映射、5类连接异常优先级校验;支持子电路嵌套信号传递、多层电路迭代仿真。
工程指标:代码总行数突破 521行,全局总圈复杂度 271,平均方法复杂度 7.32,是三份作业中逻辑最复杂、边界场景最多的版本。
二、三层能力考核体系
-
基础层(作业4):考核基础面向对象继承、字符串正则拆分、BFS队列信号传播,巩固Java类、抽象类、集合容器核心基础。
-
进阶层(作业5):考核多子类重写求值方法、多类型引脚索引映射、迭代仿真收敛判断;核心难点为控制/输入/输出三类引脚编号区分,极易出现索引越界Bug。
-
高阶层(作业6):考核组合模式、多层嵌套电路仿真、多优先级异常判断、子电路内外引脚隔离映射;难点集中在异常优先级排序、子电路与主电路信号双向流通、引脚冲突检测,同时需完全兼容前序所有功能。
三、核心知识点覆盖
本次项目完整覆盖Java后端面向对象开发核心考点:
-
抽象类、方法重写、多子类继承扩展
-
HashMap/List/LinkedHashMap集合容器实战
-
自定义实体类(Pin)、重写equals与hashCode
-
字符串解析、数字提取、正则拆分
-
循环收敛仿真、分层输入解析
-
多分支异常判断、组合模式复合对象管理
四、各版本程序设计与复杂度分析
4.1 作业4:基础门电路程序设计
4.1.1 UML类图结构

整体采用四层极简结构,完全基于面向对象继承实现:
-
Main入口类:全局存储组件映射、输入信号、引脚连接关系;封装输入解析、仿真、打印核心方法,包含
parseConnection、simulateCircuit等高复杂度核心函数。 -
Pin实体类:封装「元件名+引脚号」二元唯一标识,重写
equals()与hashCode(),适配HashMap键匹配规则。 -
Component抽象父类:统一定义所有门电路通用属性(名称、输入列表、输出值、计算标记),声明抽象求值方法
calculateOutput();封装hasAllInputs()、setInput()公共工具方法。 -
5大基础门子类:AndGate、OrGate、NotGate、XorGate、XnorGate,独立重写求值方法,实现对应布尔逻辑。
4.1.2 代码复杂度指标分析

基于MetricsReloaded生成的检测报表,核心指标如下:
-
全局总圈复杂度 CogC=151,平均单方法复杂度 6.04,属于中小型规范工程。
-
核心高复杂度方法:
simulateCircuit()(CogC=83、v(G)=28),包含多层循环、条件分支、队列遍历,是仿真核心与Bug高发区。 -
解析方法
extractNumber()、parseConnection()复杂度偏高,大量索引截取、字符串判断分支。 -
各类门
calculateOutput()复杂度稳定在2~4,逻辑单一,符合单一职责原则。 -
所有构造方法复杂度为0,仅做变量初始化,轻量化规范达标。
4.1.3 设计缺陷与心得
核心代码片段:抽象父类 Component
abstract class Component {
String name;
String type;
int numInputs;
List<Integer> inputValues;
Integer outputValue;
boolean outputCalculated;
Component(String name, String type, int numInputs) {
this.name = name;
this.type = type;
this.numInputs = numInputs;
this.inputValues = new ArrayList<>(Collections.nCopies(numInputs, null));
this.outputValue = null;
this.outputCalculated = false;
}
// 抽象求值方法,各逻辑门实现
abstract void calculateOutput();
// 判断所有输入引脚是否均有有效电平
boolean hasAllInputs() {
for (Integer value : inputValues) {
if (value == null) return false;
}
return true;
}
// 为指定输入引脚赋值,重置计算标记
void setInput(int pinIndex, int value) {
if (pinIndex >= 0 && pinIndex < numInputs) {
inputValues.set(pinIndex, value);
}
outputCalculated = false;
}
}
核心代码片段:BFS 单次信号仿真
static void simulateCircuit() {
Queue<Pin> signalQueue = new LinkedList<>();
Set<Pin> processedOutputs = new HashSet<>();
// 全局输入信号入队
for (Map.Entry<String, Integer> entry : inputSignals.entrySet()) {
Pin sourcePin = new Pin(entry.getKey(), -1);
if (connections.containsKey(sourcePin)) {
for (Pin target : connections.get(sourcePin)) {
Component comp = components.get(target.componentName);
if (target.pinNumber >= 1 && target.pinNumber <= comp.numInputs) {
comp.setInput(target.pinNumber - 1, entry.getValue());
signalQueue.add(target);
}
}
}
}
// 逐层传递信号
while (!signalQueue.isEmpty()) {
Pin currentPin = signalQueue.poll();
Component comp = components.get(currentPin.componentName);
comp.calculateOutput();
Pin outputPin = new Pin(comp.name, 0);
// 将当前门输出传递给下游元件
if (connections.containsKey(outputPin) && !processedOutputs.contains(outputPin)) {
processedOutputs.add(outputPin);
for (Pin target : connections.get(outputPin)) {
Component targetComp = components.get(target.componentName);
targetComp.setInput(target.pinNumber - 1, comp.outputValue);
signalQueue.add(target);
}
}
}
}
本次版本结构直观、开发高效,但存在明显架构短板:
-
耦合严重:Main类包揽解析、仿真、打印全部业务,成为巨型类,违背单一职责;核心仿真方法长达百行,可读性、可维护性极差。
-
扩展性弱:无控制引脚、多路输出引脚预留,无法直接扩展三态门、译码器等复合元件。
-
仿真能力有限:采用一次性BFS队列传播信号,不支持带反馈的循环电路,无迭代收敛逻辑。
-
实体设计简陋:Pin实体与Component强耦合,无引脚分层抽象,为后续重构埋下隐患。
4.2 作业5:多引脚组合元件扩展设计
4.2.1 UML类图结构

针对作业1缺陷完成核心分层重构,优化整体架构:
-
轻量化Main类:拆分冗余逻辑,新增统一迭代仿真方法
simulate(),封装引脚操作工具方法setPinValue、getPinNum。 -
全面重构Component抽象类:区分控制、输入、输出三类引脚,新增独立计数器与数据存储列表;统一封装
isReady()、isValid()、getOutputPinNumber()通用方法。 -
新增4类复合元件:TriStateGate(三态门)、Decoder(译码器)、Multiplexer(数据选择器)、Demultiplexer(数据分配器)。
-
统一接口规范:所有元件实现
evaluate()求值方法,替代旧版分散接口,统一仿真调用入口。
4.2.2 代码复杂度指标分析


-
全局总圈复杂度上涨至 166,平均复杂度降至 5.03,架构拆分有效分散了代码分支压力。
-
核心方法
Main.simulate()(CogC=39、v(G)=15),新增循环迭代收敛机制,支持电路信号持续刷新直至稳定。 -
译码器求值方法
Decoder.evaluate()复杂度=8,包含控制引脚校验、二进制编码转换、多路输出赋值多分支逻辑。 -
结果打印方法
Main.printResults()复杂度=33,需适配9类元件差异化输出规则,存在大量类型判断分支。 -
基础门求值方法复杂度保持2~4,原有逻辑完全复用,扩展成本极低。
4.2.3 设计心得与遗留问题
核心代码片段:分层引脚抽象 Component
abstract class Component {
String name;
String type;
// 三类引脚计数分离
int numControlPins;
int numInputPins;
int numOutputPins;
List<Integer> controlValues;
List<Integer> inputValues;
List<Integer> outputValues;
boolean evaluated;
Component(String name, String type, int numControlPins, int numInputPins, int numOutputPins) {
this.name = name;
this.type = type;
this.numControlPins = numControlPins;
this.numInputPins = numInputPins;
this.numOutputPins = numOutputPins;
controlValues = new ArrayList<>(Collections.nCopies(numControlPins, null));
inputValues = new ArrayList<>(Collections.nCopies(numInputPins, null));
outputValues = new ArrayList<>(Collections.nCopies(numOutputPins, null));
evaluated = false;
}
// 统一求值接口,所有元件实现
abstract void evaluate();
// 判断控制、输入引脚是否全部赋值完成
boolean isReady() {
for (Integer v : controlValues) if (v == null) return false;
for (Integer v : inputValues) if (v == null) return false;
return true;
}
// 根据引脚编号自动区分控制/输入引脚赋值
void setPinValue(int pinNumber, int value) {
if (pinNumber < numControlPins) {
controlValues.set(pinNumber, value);
} else if (pinNumber < numControlPins + numInputPins) {
inputValues.set(pinNumber - numControlPins, value);
}
evaluated = false;
}
boolean isValid() { return evaluated && isReady(); }
// 转换输出引脚对外展示编号
int getOutputPinNumber(int index) {
return numControlPins + numInputPins + index;
}
}
核心代码片段:迭代收敛仿真逻辑
static void simulate() {
// 初始化全局输入信号
for (Map.Entry<String, Integer> entry : inputSignals.entrySet()) {
List<String> targets = connections.get(entry.getKey());
if (targets != null) {
for (String tgt : targets) setPinValue(tgt, entry.getValue());
}
}
int maxIterations = components.size() * 100;
int iterations = 0;
boolean changed;
// 循环迭代,直到本轮无任何元件输出变更
do {
changed = false;
iterations++;
for (Component comp : components.values()) {
if (!comp.isReady()) continue;
List<Integer> oldOutputs = new ArrayList<>(comp.outputValues);
comp.evaluate();
if (!comp.isValid()) continue;
// 对比新旧输出,发生变化则向下游传递信号
for (int i = 0; i < comp.outputValues.size(); i++) {
Integer newVal = comp.outputValues.get(i);
Integer oldVal = oldOutputs.get(i);
if (!Objects.equals(newVal, oldVal)) {
int pinNum = comp.getOutputPinNumber(i);
String pinStr = comp.name + "-" + pinNum;
List<String> tgts = connections.get(pinStr);
if (tgts != null && newVal != null) {
for (String tgt : tgts) setPinValue(tgt, newVal);
changed = true;
}
}
}
}
} while (changed && iterations < maxIterations);
}
本次迭代解决了初代版本的扩展性痛点,分层设计优势显著:
-
引脚分层抽象,新增元件无需修改全局解析逻辑,仅需新增子类,符合开闭原则。
-
迭代仿真替代一次性BFS,支持环路组合电路,仿真场景更全面。
同时存在明显遗留缺陷:未引入组合模式,所有元件均为独立叶子对象,无法封装复用电路模块;完全缺失输入异常校验,无法识别非法连接、引脚冲突等异常场景。
4.3 作业6:子电路+异常校验综合架构设计
4.3.1 UML组合模式类图结构

引入标准Composite组合模式,实现电路单元统一管理与嵌套复用,分为三层核心结构:
-
顶层抽象构件 AbstractComponent:统一所有电路单元(基础门、子电路)通用接口,包含
isReady()、evaluate()、setPinValue()等核心方法,实现叶子与容器对外接口统一。 -
叶子构件 BasicGate:整合所有基础逻辑门,仅承载单一门电路逻辑,无内部子元件,实现顶层抽象接口。
-
容器构件 SubCircuit:复合子电路对象,独立维护内部元件、内部连接、引脚映射、私有信号;支持电路嵌套解析、内部仿真、对外引脚暴露。
-
全局Main入口:维护全局元件与子电路容器,新增5类优先级异常校验、引脚冲突检测、内外电路信号双向映射能力。
4.3.2 代码复杂度指标分析


-
全局总圈复杂度 271,平均单方法复杂度 7.32,项目规模与逻辑复杂度大幅提升。
-
最高复杂度方法:
SubCircuit.evaluate()(CogC=35、v(G)=17),嵌套多层循环,同步刷新内部元件、信号线、引脚状态。 -
连接解析方法
Main.parseConnection()(CogC=23),包含5层优先级异常判断分支。 -
全局仿真方法
Main.simulate()(CogC=42),实现主电路+子电路多层信号同步迭代刷新。 -
打印方法需区分基础门、嵌套子电路双格式,拼接子电路编号前缀,分支逻辑大幅增加。
4.3.3 设计心得与可优化点
顶层抽象构件 AbstractComponent(组合模式统一接口)
abstract class AbstractComponent {
String name;
String type;
AbstractComponent(String name, String type) {
this.name = name;
this.type = type;
}
// 叶子门、子电路统一对外接口
abstract boolean isReady();
abstract void evaluate();
abstract boolean isValid();
abstract void setPinValue(int pinNumber, int value);
abstract Map<String, Integer> getOutputValues();
}
核心代码片段:连接语句异常优先级校验
static void parseConnection(String line) {
String mid = line.substring(1, line.length() - 1).trim();
String[] parts = mid.split("\\s+");
// 优先级3:无任何引脚
if (parts.length == 1) {
errorMessage = "ERROR: " + line + " include none output";
return;
}
int cntIn = 0, cntOut = 0;
for (String p : parts) {
if (isGlobalInput(p)) cntIn++;
if (isGlobalOutput(p)) cntOut++;
}
// 优先级1:多条输入源
if (cntIn > 1) {
errorMessage = "ERROR: " + line + " include more than one input";
return;
}
// 优先级2:无目标输入引脚
if (cntOut == 0) {
errorMessage = "ERROR: " + line + " include none input";
return;
}
// 优先级4:首位不是输出源,顺序颠倒
if (!isGlobalInput(parts[0])) {
errorMessage = "ERROR: " + line + " input and output sequence error";
return;
}
// 优先级5:同一输入引脚绑定多路信号冲突
for (int i = 1; i < parts.length; i++) {
String tgt = parts[i];
if (pinSourceMap.containsKey(tgt)) {
errorMessage = "ERROR: " + tgt + " input signal conflict";
return;
}
}
// 无异常则记录连接关系
for (int i = 1; i < parts.length; i++) {
pinSourceMap.put(parts[i], parts[0]);
}
mainConnections.add(Arrays.copyOf(parts, parts.length));
}
组合模式是本次迭代最大优化,彻底解决前两版核心短板:
-
面向顶层接口编程,主电路无需区分普通门与子电路,新增复用模块无需改动核心逻辑。
-
子电路独立维护内部命名空间,元件、信号完全隔离,避免全局命名冲突。
-
异常校验按优先级有序执行,匹配即终止,避免异常覆盖问题。
遗留可优化点:字符串解析逻辑全部集中于Main类、异常提示硬编码、仿真迭代阈值固定,灵活性与可维护性有待提升。
五、全版本踩坑复盘(现象+定位+解决方案)
5.1 作业1高频Bug
坑1:单次BFS遍历,多级串联电路信号传递失效
-
现象:多级串联电路(与门→非门→或门)后级输入为null,输出为空。
-
根因:
simulateCircuit仅单次队列遍历,信号仅传递一级,下游元件无法二次更新。 -
修复:改为循环迭代仿真,持续遍历所有元件,直至电路输出无变更、达到收敛状态。
坑2:引脚编号与数组下标错位,引发越界异常
-
现象:输入引脚编号1~n,直接作为List下标使用,触发
IndexOutOfBoundsException。 -
测试用例:
[A A(2)1-2],引脚2直接赋值下标2,数组越界。 -
修复:统一执行
pinIndex - 1偏移转换,适配数组从0开始的下标规则。
坑3:数字解析逻辑缺陷,括号内数字误读取
-
现象:解析
A(4)2时,错误提取编号4而非目标编号2,多输入门、译码器编号错乱。 -
根因:无括号状态标记,所有数字统一读取,未区分参数括号与业务编号。
-
修复:新增布尔标记位,跳过括号内数字,仅提取括号外有效业务编号。
5.2 作业2高频Bug
坑1:三类引脚索引混淆,控制引脚赋值错位
-
现象:三态门控制引脚赋值时,错误写入普通输入列表,求值逻辑完全失效。
-
根因:新增控制/输入/输出三套引脚存储,但赋值方法未做区间分层判断。
-
修复:按区间判定引脚类型:优先匹配控制引脚、再匹配输入引脚、剩余为输出引脚。
坑2:译码器控制引脚合法判断分支遗漏
-
现象:译码器控制引脚不满足有效条件时,仍输出有效电平,不符合题目约束。
-
根因:
Decoder.evaluate方法分支不全,缺少多控制引脚联合校验逻辑。 -
修复:新增前置联合判断,非法控制引脚状态直接标记输出无效。
坑3:数据分配器无效引脚无占位输出
-
现象:无效输出引脚空白,未按要求打印
-占位符,输出格式不规范。 -
修复:通过三元运算符统一填充:
v == null ? "-" : v
5.3 作业4高频边界Bug(核心难点)
坑1:异常判断优先级颠倒,错误提示覆盖
-
题目约束优先级:多输入 > 无输入 > 无输出 > 顺序颠倒 > 引脚冲突
-
现象:初期先检测引脚冲突,导致同时存在多输入+引脚冲突的用例,输出错误提示。
-
修复:严格按题目优先级编写判断分支,匹配任一异常立即赋值提示并终止解析。
坑2:子电路与主电路元件命名冲突
-
现象:子电路内部元件与主电路元件编号重复,信号值互相覆盖。
-
根因:初期全局容器共用,未隔离子电路命名空间。
-
修复:拆分双层容器,主电路存全局元件,SubCircuit独立维护内部私有元件,打印时拼接子电路前缀区分。
坑3:子电路引脚双向映射失效
-
现象:全局信号无法传入
C1-A格式的子电路引脚。 -
修复:新增子电路引脚识别工具方法,匹配
C数字-引脚名格式,完成内外信号映射赋值。
坑4:子电路输出引脚冲突检测遗漏
-
现象:子电路输出与全局输入绑定同一目标引脚,未触发冲突异常。
-
修复:将全局输入、门输出、子电路输出统一纳入冲突检测Map,全覆盖校验。
坑5:子电路反馈电路仿真无限循环
-
现象:带反馈的嵌套子电路无迭代上限,仿真死循环、程序超时。
-
修复:设置最大迭代上限2000次,循环递减计数,超时自动终止仿真,保证运行时效。
六、可持续迭代优化方案
6.1 架构分层:拆解Main巨型类
当前所有核心逻辑集中于Main类,耦合度极高,可拆分三大独立工具类:
-
InputParser解析工具类:承载所有输入解析、子电路解析、异常校验逻辑,对外暴露统一解析接口。
-
Simulator仿真工具类:独立封装全局、子电路双层迭代仿真、收敛判断逻辑。
-
OutputPrinter打印工具类:统一管理所有元件、子电路输出格式,集中维护异常文案常量。
分层后Main仅保留程序入口,彻底解耦业务逻辑。
6.2 常量统一管理,消除硬编码
将项目中所有硬编码内容统一封装为常量/枚举:
-
元件标识、子电路前缀存入
ComponentType枚举 -
所有异常提示文案统一静态常量管理
-
仿真迭代阈值、引脚偏移量统一定义
大幅降低代码维护成本,避免字符串书写错误引发Bug。
6.3 实体类封装优化,精简冗余代码
-
新增
PinInfo引脚实体:自动拆分电路、元件、引脚信息,提供类型判断工具方法,消除重复字符串解析代码。 -
新增
SignalWire信号线实体:统一管理信号源与目标引脚关系,为后续短路异常检测提供支撑。
6.4 完善异常体系,实现全场景校验
在现有5类连接异常基础上,扩展全场景语法校验:
-
元件命名正则合法性校验
-
引脚编号越界异常校验
-
子电路、元件重复编号校验
-
输出引脚短路冲突校验
自定义CircuitException异常类,携带错误码、行号、描述,精准定位问题。
6.5 仿真性能优化
-
增量更新:仅重新仿真信号变更的元件,无需全局遍历,仿真效率提升80%+。
-
动态迭代上限:根据电路元件数量自适应调整最大迭代次数。
-
子电路结果缓存:输入无变更时复用上次仿真结果,避免重复计算。
6.6 设计模式扩展,适配时序电路迭代
现有组合模式可无缝扩展后续时序电路(D触发器、JK触发器)开发:
-
时序元件继承顶层抽象构件,重写求值方法,新增时钟状态存储。
-
新增全局时钟调度器,区分组合逻辑与时序逻辑仿真规则。
-
子电路兼容时序元件,支持时序模块嵌套复用。
七、整体总结
7.1 阶段学习收获
-
面向对象落地能力:完成从简单继承到标准组合模式的架构迭代,熟练掌握单一职责、开闭原则的工程落地,具备迭代兼容式开发思维。
-
工程化代码优化能力:通过复杂度报表定位代码短板,掌握长方法拆分、分支分散、架构重构的优化思路。
-
仿真算法设计能力:吃透数字电路信号传播逻辑,掌握迭代收敛、多层嵌套电路信号双向流通的仿真方案。
-
语法解析与异常处理能力:熟练手写自定义语法解析逻辑,掌握多优先级边界异常的有序处理方案。
-
迭代开发思维:建立「先兼容、后扩展」的开发规范,有效降低迭代重构的Bug风险。
7.2 待深入提升方向
-
设计模式深度落地:引入工厂模式优化元件创建逻辑、策略模式优化求值算法,消除冗余分支。
-
标准化语法解析:学习编译原理基础,使用词法分析替代手写字符串拆分,提升解析健壮性。
-
时序电路仿真:深入学习时钟边沿触发、状态存储等时序电路核心仿真逻辑。
-
自动化测试体系:搭建JUnit单元测试,实现功能自动化回归,避免迭代引入历史Bug。
-
容器性能调优:深耕Java集合容器特性,针对仿真高频场景优化存储结构,降低时间复杂度。
本次三次迭代开发完整模拟了工业软件小规模产品的迭代流程,从基础功能开发、特性扩展、架构重构到健壮性优化,全方位锻炼了Java工程化开发与架构设计思维,为后续大型面向对象项目开发筑牢基础。

浙公网安备 33010602011771号