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类图结构

屏幕截图 2026-06-24 175400

整体采用四层极简结构,完全基于面向对象继承实现:

  1. Main入口类:全局存储组件映射、输入信号、引脚连接关系;封装输入解析、仿真、打印核心方法,包含parseConnectionsimulateCircuit等高复杂度核心函数。

  2. Pin实体类:封装「元件名+引脚号」二元唯一标识,重写equals()hashCode(),适配HashMap键匹配规则。

  3. Component抽象父类:统一定义所有门电路通用属性(名称、输入列表、输出值、计算标记),声明抽象求值方法calculateOutput();封装hasAllInputs()setInput()公共工具方法。

  4. 5大基础门子类:AndGate、OrGate、NotGate、XorGate、XnorGate,独立重写求值方法,实现对应布尔逻辑。

4.1.2 代码复杂度指标分析

屏幕截图 2026-06-24 180015

基于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类图结构

屏幕截图 2026-06-24 180258

针对作业1缺陷完成核心分层重构,优化整体架构:

  1. 轻量化Main类:拆分冗余逻辑,新增统一迭代仿真方法simulate(),封装引脚操作工具方法setPinValuegetPinNum

  2. 全面重构Component抽象类:区分控制、输入、输出三类引脚,新增独立计数器与数据存储列表;统一封装isReady()isValid()getOutputPinNumber()通用方法。

  3. 新增4类复合元件:TriStateGate(三态门)、Decoder(译码器)、Multiplexer(数据选择器)、Demultiplexer(数据分配器)。

  4. 统一接口规范:所有元件实现evaluate()求值方法,替代旧版分散接口,统一仿真调用入口。

4.2.2 代码复杂度指标分析

屏幕截图 2026-06-24 180337
屏幕截图 2026-06-24 180418

  • 全局总圈复杂度上涨至 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组合模式类图结构

屏幕截图 2026-06-24 180657

引入标准Composite组合模式,实现电路单元统一管理与嵌套复用,分为三层核心结构:

  1. 顶层抽象构件 AbstractComponent:统一所有电路单元(基础门、子电路)通用接口,包含isReady()evaluate()setPinValue()等核心方法,实现叶子与容器对外接口统一。

  2. 叶子构件 BasicGate:整合所有基础逻辑门,仅承载单一门电路逻辑,无内部子元件,实现顶层抽象接口。

  3. 容器构件 SubCircuit:复合子电路对象,独立维护内部元件、内部连接、引脚映射、私有信号;支持电路嵌套解析、内部仿真、对外引脚暴露。

  4. 全局Main入口:维护全局元件与子电路容器,新增5类优先级异常校验、引脚冲突检测、内外电路信号双向映射能力。

4.3.2 代码复杂度指标分析

屏幕截图 2026-06-24 180720
屏幕截图 2026-06-24 180731

  • 全局总圈复杂度 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工程化开发与架构设计思维,为后续大型面向对象项目开发筑牢基础。

posted @ 2026-06-24 19:12  君愿  阅读(3)  评论(0)    收藏  举报