面向对象程序设计课程阶段性总结报告

一、前言
1.1 三次作业集概览
项目 第一次作业 第二次作业 第三次作业
主题 数字电路逻辑门仿真 扩展电路组件仿真 子电路模块化仿真
题量 1道综合题 1道综合题 1道综合题
难度 ★★★ ★★★★ ★★★★★
知识点 正则表达式、枚举类型、Map集合、拓扑排序 继承多态、抽象类、工厂模式、队列传播 递归解析、模块化设计、作用域管理、信号驱动
1.2 知识体系演进
三次作业形成了一个完整的数字电路仿真器开发序列:
第一次:建立基础框架,实现5种基本逻辑门(AND、OR、NOT、XOR、XNOR)的解析、连接与计算
第二次:引入面向对象设计,新增三态门、解码器、数据选择器、数据分配器等复杂组件
第三次:实现子电路(SubCircuit)的递归定义与实例化,支持模块化层次化设计

二、设计与分析
2.1 第一次作业源码分析
类图设计
┌─────────────────────────────────────────────────────────────┐
│ Main │
├─────────────────────────────────────────────────────────────┤
│ - inputSignals: Map<String,Integer> │
│ - gateMap: Map<String,LogicGate> │
│ - connections: Map<String,List> │
├─────────────────────────────────────────────────────────────┤
│ + main(String[]) │
│ - parseInputLine(String) │
│ - parseConnectionLine(String) │
│ - parseAndCreateGate(String) │
│ - getSignal(String): int │
│ - propagateSignals() │
│ - printResult() │
└─────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│ LogicGate │
├─────────────────────────────────────────────────────────────┤
│ - type: GateType │
│ - id: int │
│ - inputNum: int │
│ - inputPins: Map<Integer,Integer> │
│ - output: int │
│ - valid: boolean │
├─────────────────────────────────────────────────────────────┤
│ + setInput(pin, value) │
│ + calculate() │
│ + getName(): String │
│ + compareTo(LogicGate): int │
└─────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│ GateType (enum) │
├─────────────────────────────────────────────────────────────┤

│ AND, OR, NOT, XOR, XNOR │
└─────────────────────────────────────────────────────────────┘
SourceMonitor分析报告
QQ20260624-231111

核心设计思路

  1. 门电路计算逻辑
    java
    // 以AND门为例的简化计算
    case AND:
    boolean andFlag = true;
    for (int val : inputs) {
    if (val != 1) { andFlag = false; break; }
    }
    output = andFlag ? 1 : 0;
    break;
  2. 信号传播机制
    采用两次遍历策略:
    第一次遍历connections,从输入信号源开始向下游传播
    第二次遍历所有门,计算尚未计算的输出
    这种方式的缺陷在于:无法保证拓扑顺序,对于多级级联的门电路可能产生不正确的结果(因为依赖的门可能尚未计算)。
  3. 命名解析正则表达式
    java
    // 解析带输入数量的AND/OR门: A(2)1, O(3)5
    private static final Pattern GATE_AND_OR = Pattern.compile("([AO])\((\d+)\)(\d+)");
    // 解析普通门: N1, X2, Y3
    private static final Pattern GATE_NORMAL = Pattern.compile("([NXY])(\d+)");
    // 解析带引脚编号: A(2)1-0, N1-1
    private static final Pattern PIN_PATTERN = Pattern.compile("(.+)-(\d+)");
    心得体会
    第一次作业让我深刻体会到了数据结构设计的重要性。Map<String, LogicGate>存储门实例,Map<String, List>存储连接关系,这种设计使得后续的传播和输出变得直观。
    但也暴露了一个问题:信号传播的时序性。由于没有采用队列机制,多级门电路的计算顺序可能出错,这为后续作业的改进埋下了伏笔。

2.2 第二次作业源码分析
类图设计
text
┌──────────────────────────────────────────────────────────────┐
│ Component (abstract) │
├──────────────────────────────────────────────────────────────┤
│ # name: String │
│ # inputValues: Map<Integer,Integer> │
│ # output: Integer │
├──────────────────────────────────────────────────────────────┤
│ + canCompute(): boolean │
│ + compute(): void │
│ + getOutputString(): String │
└──────────────────────────────────────────────────────────────┘
▲ ▲ ▲ ▲ ▲
│ │ │ │ │
┌─────────┴─────┐ ┌───┴─────────┐ ┌─────────┴───┐ ┌─────┴──────────┐
│ AndGate │ │ OrGate │ │ NotGate │ │ XorGate │
│ - numInputs │ │ - numInputs │ │ │ │ │
└───────────────┘ └─────────────┘ └─────────────┘ └────────────────┘
▲ ▲ ▲ ▲
│ │ │ │
┌─────────┴───────────┴───────────┴───────────┴────────────┐
│ │
│ ┌─────────────┐ ┌──────────┐ ┌────────────┐ ┌─────────┐│
│ │TriStateGate │ │ Decoder │ │DataSelector│ │DataDist ││
│ └─────────────┘ └──────────┘ └────────────┘ └─────────┘│
└───────────────────────────────────────────────────────────┘
SourceMonitor分析报告
QQ20260624-231323
核心设计改进

  1. 抽象类设计
    java
    abstract class Component {
    String name;
    Map<Integer, Integer> inputValues;
    Integer output;

    abstract boolean canCompute(); // 检查输入是否就绪
    abstract void compute(); // 计算输出
    abstract String getOutputString();
    }
    每个子类只需实现自己的compute()和canCompute()方法,大大降低了耦合度。

  2. 信号队列传播
    java
    Queue signalQueue = new LinkedList<>();
    while (!signalQueue.isEmpty()) {
    String source = signalQueue.poll();
    // 传播到所有目标
    for (String target : targets) {
    // 更新组件输入
    comp.inputValues.put(pinNum, signalValues.get(source));
    comp.compute();
    // 如果计算有效,将输出加入队列
    if (comp.output != null) {
    signalQueue.offer(outKey);
    }
    }
    }
    关键改进:使用BFS队列确保信号按拓扑顺序传播,解决了第一次作业中的级联问题。

  3. 新增组件的实现
    以解码器(Decoder)为例:
    java
    class Decoder extends Component {
    int numInputs;
    int numOutputs;
    Integer activeOutput;

    @Override
    void compute() {
    // 检查使能信号: S1=1, S2=0, S3=0
    if (s1 == 1 && s2 + s3 == 0) {
    // 编码输入为输出索引
    int encoded = 0;
    for (int i = 0; i < numInputs; i++) {
    encoded |= (inputValues.get(3 + i) << i);
    }
    activeOutput = encoded;
    }
    }
    }
    心得体会
    第二次作业最核心的收获是面向对象设计原则的实践:
    开闭原则:通过继承Component抽象类,新增组件无需修改已有代码
    单一职责原则:每个Gate类只负责一种逻辑运算
    依赖倒置:Main类依赖于Component抽象,而非具体实现
    队列传播机制的引入,使得仿真结果更加准确,能够正确处理多级门电路的级联场景。

2.3 第三次作业源码分析
类图设计
text
┌──────────────────────────────────────────────────────────────────┐
│ DigitalCircuitSim │
├──────────────────────────────────────────────────────────────────┤
│ - subCircuitMap: Map<String,SubCircuit> │
│ - mainInputMap: Map<String,Integer> │
│ - allConnLines: List
│ - pinDriverMap: Map<String,String> │
│ - allGates: List
├──────────────────────────────────────────────────────────────────┤
│ + parseInput(Scanner) │
│ - parseSubCircuit(Scanner, String) │
│ - parseMainInput(String) │
│ - checkAllConnections() │
│ - computeAllSignals() │
│ - outputAllGates() │
└──────────────────────────────────────────────────────────────────┘

┌───────────────────┼───────────────────┐
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────────┐
│ CircuitNode │ │ SubCircuit │ │ Gate │
│ (abstract) │ ├─────────────────┤ ├─────────────────────────┤
├─────────────────┤ │ - subNo: String │ │ - type: GateType │
│ - id: String │ │ - inPorts │ │ - numInPin: int │
└─────────────────┘ │ - outPorts │ │ - gateNo: int │
│ - connLines │ │ - subCirNo: String │
│ - gateMap │ │ - inputPins: Map │
│ - portSignal │ │ - outVal: Integer │
└─────────────────┘ ├─────────────────────────┤
│ + calcOutput(): Integer│
│ + getOutputPinFullName │
└─────────────────────────┘
SourceMonitor分析报告
QQ20260624-231511

核心设计思路

  1. 子电路定义与存储
    java
    class SubCircuit extends CircuitNode {
    String subNo;
    List inPorts = new ArrayList<>(); // INPUT: 端口列表
    List outPorts = new ArrayList<>(); // OUT: 端口列表
    List connLines = new ArrayList<>(); // 内部连接
    Map<String, Gate> gateMap = new HashMap<>(); // 内部门
    Map<String, Integer> portSignal = new HashMap<>(); // 端口信号值
    }
  2. 解析子电路的语法
    text
    C1:
    INPUT: a b c
    OUT: d e
    [A(2)1 a b]
    endc
    解析器需要处理嵌套结构,在遇到C开头的行时进入子电路解析模式,直到遇到endc退出。
  3. 信号冲突检测
    java
    private void checkAllConnections() {
    for (String conn : allConnLines) {
    // 检查是否有多个输出源
    int outputCount = countOutputInTokens(tokens);
    if (outputCount > 1) {
    errorOccur = true;
    errorMsg = "ERROR: " + conn + " include more than one input";
    return;
    }
    // 检查输出是否在第一位
    if (!isOutputPin(outputPart)) {
    errorOccur = true;
    errorMsg = "ERROR: " + conn + " input and output sequence error";
    return;
    }
    // 检查是否有多驱动冲突
    for (String inPin : inputParts) {
    if (pinDriverMap.containsKey(inPin)) {
    errorOccur = true;
    errorMsg = "ERROR: " + inPin + " input signal conflict";
    return;
    }
    pinDriverMap.put(inPin, outputPart);
    }
    }
    }
  4. 信号获取的递归查询
    java
    private Integer getSignalValue(String src) {
    // 1. 检查主输入
    if (mainInputMap.containsKey(src)) return mainInputMap.get(src);
    // 2. 检查门的输出
    if (src.contains("-0")) {
    String gateId = src.substring(0, src.lastIndexOf("-0"));
    for (Gate g : allGates) {
    if (g.id.equals(gateId)) return g.outVal;
    }
    }
    // 3. 检查子电路端口
    if (src.contains("-")) {
    String[] seg = src.split("-");
    String subNo = seg[0];
    String port = seg[1];
    SubCircuit sub = subCircuitMap.get(subNo);
    return sub.portSignal.get(port);
    }
    return null;
    }
    心得体会
    第三次作业的核心挑战是递归子电路的处理。由于每个子电路内部可能包含对其他子电路的引用,信号查询需要能够跨层级传递。
    设计权衡:为了简化复杂度,第三次没有使用队列传播,而是采用分阶段计算:
    首先构建所有子电路的门实例
    然后填充每个门的输入
    最后统一计算输出
    这种方式的局限性在于:如果子电路之间存在信号依赖,可能需要多次迭代计算才能收敛。

三、采坑心得
3.1 第一次作业的坑
坑1:信号传播顺序问题
问题描述:对于连接A(2)1 -> X1 -> Y1这样的级联,有时Y1的输出为null。

原因分析:在propagateSignals()中,只遍历了connections一次,没有处理多级依赖。

text
输入: A(2)1的输出连接到X1的输入,X1的输出连接到Y1的输入
期望: A(2)1计算 → X1计算 → Y1计算
实际: X1计算时A(2)1尚未计算 → X1输出null → Y1输出null
解决方案:使用Queue进行BFS传播,并将connections改为从信号源到目标的映射。

坑2:正则表达式匹配的贪婪性
java
// 错误写法:会把"A(2)10"中的"10"整体匹配为id
Pattern.compile("([AO])\((\d+)\)(\d+)");
// 正确写法:明确边界
Pattern.compile("([AO])\((\d+)\)(\d+)(?![\d])");
3.2 第二次作业的坑
坑3:Decode器的引脚编号混淆
问题描述:Decoder有使能引脚(0,1,2)和数据输入引脚(3,4,5...),容易混淆。

java
// 错误:将数据输入当成使能信号
int encoded = 0;
for (int i = 0; i < numInputs; i++) {
encoded |= (inputValues.get(i) << i); // 错误!应该是3+i
}

// 正确
for (int i = 0; i < numInputs; i++) {
encoded |= (inputValues.get(3 + i) << i);
}
坑4:三态门高阻态的处理
三态门在使能为0时输出高阻态(null),需要正确处理:

java
if (inputValues.get(0) == 1) {
output = inputValues.get(1);
} else {
output = null; // 高阻态,不应参与后续计算
}
3.3 第三次作业的坑
坑5:子电路的递归解析
问题:子电路可以嵌套引用,如C1内部使用了C2的实例。

解决方案:采用两阶段解析法:

第一阶段:读取所有子电路定义,建立subCircuitMap

第二阶段:解析连接关系,构建门实例

java
// 第一阶段:只存储定义
while (sc.hasNextLine()) {
if (line.startsWith("C") && line.contains("😊) {
parseSubCircuit(sc, line); // 只存储,不解析门
}
}
// 第二阶段:解析所有连接
for (String conn : allConnLines) {
// 构建门实例
buildGatesFromConnection(conn);
}
坑6:子电路端口信号的传递
当子电路作为信号源时,需要将其输出端口的值传递给外部信号:

java
// 子电路输出端口
if (isSubCircuitOutput(src)) {
String subNo = src.split("-")[0];
String port = src.split("-")[1];
SubCircuit sub = subCircuitMap.get(subNo);
// 需要先计算子电路内部的信号
computeSubCircuit(sub);
return sub.portSignal.get(port);
}
3.4 共性坑:空指针与异常处理
三次作业中多次遇到NullPointerException,主要原因:

门实例未创建就被引用

输入引脚未设置就调用calculate()

连接的源信号不存在

解决方案:增加防御性检查

java
// 防御性编程示例
private int getSignal(String pin) {
if (inputSignals.containsKey(pin)) {
return inputSignals.get(pin);
}
LogicGate gate = gateMap.get(pin);
if (gate == null) return -1; // 防御null
if (!gate.valid) {
gate.calculate();
}
return gate.valid ? gate.output : -1;
}
四、改进建议
4.1 第一次作业改进
问题:门命名格式不一致,解析逻辑分散。

建议:统一使用工厂模式创建门实例

java
class GateFactory {
public static LogicGate createGate(String name) {
// 统一解析逻辑
// 返回对应的LogicGate实例
}
}
改进后的流程图:

text
输入行 → 识别类型 → 工厂创建 → 存入gateMap → 处理连接
4.2 第二次作业改进
问题:Component的canCompute()和compute()分离,但某些组件(如Decoder)的计算逻辑复杂,耦合度高。

建议:引入状态模式管理组件的计算状态

java
enum ComputeState {
NOT_READY, // 输入不足
READY, // 可以计算
COMPUTED, // 已计算
ERROR // 计算错误
}
4.3 第三次作业改进
问题:子电路的信号传播采用分阶段计算,无法处理循环依赖和复杂的时序逻辑。

建议:引入事件驱动仿真机制

java
class Event {
long time;
String signalName;
int value;
}

class EventQueue {
PriorityQueue events;

void schedule(Event e, long delay) {
e.time = currentTime + delay;
events.offer(e);
}
}
4.4 通用改进:测试覆盖率
三次作业都缺乏单元测试,建议引入JUnit测试框架:

java
@Test
public void testAndGate() {
AndGate gate = new AndGate("A(2)1", 2);
gate.inputValues.put(1, 1);
gate.inputValues.put(2, 1);
gate.compute();
assertEquals(1, (int)gate.output);

gate.inputValues.put(2, 0);
gate.compute();
assertEquals(0, (int)gate.output);
}

五、总结
5.1 学到了什么

  1. 面向对象设计能力

从第一次的面向过程逐步过渡到第三次的面向对象设计

理解了继承、多态、抽象类在实际项目中的应用价值

掌握了工厂模式在组件创建中的使用

  1. 数据结构与算法

Map的灵活运用(门映射、连接映射、输入映射)

队列在拓扑排序和信号传播中的应用

递归在层次化数据结构处理中的应用

  1. 正则表达式与解析技术

复杂字符串的模式匹配与提取

上下文无关语法的递归下降解析

错误处理与恢复策略

  1. 软件工程实践

增量式开发:从简单门电路到复杂组件再到子电路

防御性编程:空指针检查、边界条件处理

错误检测:多驱动冲突检测、连接完整性检查

5.2 需要进一步学习的内容

  1. 设计模式深化

观察者模式:组件间的信号传播天然适合观察者模式

访问者模式:遍历电路结构进行不同的分析(如静态时序分析)

  1. 并发与并行仿真

当前的仿真是单线程顺序执行的,对于大规模电路效率较低

可以学习Java并发包,实现并行事件仿真

  1. 图形化界面

将电路结构可视化,便于调试和理解

可以学习JavaFX或Swing构建GUI

  1. 硬件描述语言(HDL)

数字电路仿真器与Verilog/VHDL有相似之处

学习HDL有助于深入理解电路设计的本质

5.3 反思与展望
三次作业构成了一个完整的增量式学习路径:

text
基础门电路 → 复杂组件 → 模块化设计
↓ ↓ ↓
数据结构 面向对象 系统架构
这个过程让我深刻体会到:好的设计不是一蹴而就的,而是在不断迭代中逐步完善的。第一次的代码虽然能够工作,但在扩展性和可维护性上存在不足;第二次通过抽象类解决了扩展问题;第三次通过模块化设计解决了复杂性问题。

未来,如果继续完善这个仿真器,可以朝以下方向发展:

支持时序逻辑(触发器、寄存器)

支持波形输出(VCD格式)

支持行为级建模(更抽象的电路描述)

提供API接口,支持程序化构建电路

这门课程不仅教会了我Java编程技能,更重要的是培养了我系统设计思维和解决问题的能力。

posted @ 2026-06-24 23:21  GCenyu  阅读(2)  评论(0)    收藏  举报