面向对象程序设计——第二次作业总结

前言

       第二次面向对象迭代编程作业结束了,以递进迭代开发,从基础门电路到复杂组合器件(三态 / 译码 / 选择器 / 分配器)再到子电路分层和异常语法校验,每一题完全继承前一题全部规则,只新增功能,代码高度复用,难度阶梯式上升。

设计与分析

第四次作业

作业要求

数字电路是一种处理离散信号的电子电路。与处理连续变化信号(如声音、温度)的模拟电路不同,数字电路只识别和运算两种基本状态:高电平(通常表示为“1”) 和 低电平(通常表示为“0”)。这正好与二进制数制系统相对应,使得数字电路成为所有计算机和数字系统的物理实现基础。 请编程实现数字电路模拟程序,电路中包含与门、或门、非门、异或门、同或门五种元件。

1)用A、O、N、X、Y 分别用作与门、或门、非门、异或门、同或门五种元件的元件标识符。 电路中的每个与门、或门用“标识符(输入引脚数)+编号”作为其元件名。电路中的每个非门、异或门、同或门用“标识符+编号”作为其元件名。不同元件的编号可以相同,如X4、Y4。 同一电路中同种元件的编号不可重复,可以不连续

2)引脚信息: 引脚信息由“元件名-引脚号”构成,。

3)电路的输入信息: 电路的输入格式: INPUT:英文空格+输入1+”-”+输入信号1+英文空格+输入2+....+输入n+”-”+输入信号n 

4)连接信息 引脚的连接信息格式: [+输出引脚+英文空格+输入引脚1+。。。。+英文空格+输入引脚+], 一个输出引脚可以连接多个输入引脚,即将输出引脚的信号传给每一个输入引脚。但一个输入引脚不能连接多个输出引脚。 输出引脚不能短接在一起。

5)输入结束信息 所有输入以end为结束标志,end之后出现的内容忽略不计

实现方式

字符串元件解析、迭代布尔运算、基础排序输出

1. 整体架构

单层平面电路,无子电路、无扩展器件、无输入异常校验,仅实现 5 种基础逻辑门仿真。

2. 数据存储结构

  1. components:key = 元件名,value=int 数组[门类型,输入引脚数量],存储所有门;
  2. connections:key = 输出源引脚,value = 目标输入引脚列表,存储所有连线;
  3. pinValues:全局引脚电平池,-1代表无有效电平,0/1 为高低电平;
  4. inputs:存储顶层外部 INPUT 输入信号。

3. 输入解析实现

  1. 逐行读取,遇到end终止;
  2. INPUT:行分割名称-电平,自动补全输出引脚后缀-0存入电平池;
  3. [连线]块:拆分首元素为输出源,剩余为目标引脚;无-的全局输入自动拼接-0;遍历所有引脚自动解析并注册元件到components;
  4. 元件解析函数parseComponent:正则区分两类元件命名
    • A(n)num/O(n)num:提取输入引脚数、标记与 / 或门;
    • Nnum/Xnum/Ynum:固定输入数 1/2,标记非 / 异或 / 同或门。

4. 电平计算仿真流程

  1. 初始信号分发:将 INPUT 全局电平一次性赋值给连线目标引脚;
  2. 迭代循环while(changed)迭代更新所有门输出,直到无电平变化:
    • 遍历所有元件,遍历其 1~n 输入引脚,校验全部引脚均有 0/1 有效电平;
    • 收集输入电平,调用computeOutput按门类型计算输出 0 引脚电平;
    • 更新门输出引脚电平,并同步传递给所有连线下游引脚;
  3. 门运算逻辑:独立 switch 分支实现与 / 或 / 非 / 异或 / 同或真值运算。

5. 输出处理

  1. 按与、或、非、异或、同或分 5 个列表收集有效元件(输出不为 - 1);
  2. extractNumber正则提取元件末尾编号,同类按编号升序排序;
  3. 输出:元件名-0:电平;缺失输入无法计算的元件直接忽略。

代码规模

第4次作业代码规模如下图。

屏幕截图 2026-06-24 135810

复杂度分析如下。

屏幕截图 2026-06-24 140135

可以看出,main()复杂度 42、110 行语句、最大嵌套 7,包揽输入解析、信号传播、电路计算、排序输出全部逻辑;computeOutput()复杂度 11、19 行,5 种门逻辑分支堆叠;parseComponent()复杂度 6、13 行,两种元件命名格式正则解析判断。语句大量集中在深度 2、3、4 层级,最大嵌套达到 7 层,多层 for 循环内嵌套多重 if 判断。

第五次作业

作业要求

请编程实现数字电路模拟程序。 以下内容中,首行用#号标注的为本次新增的题目要求,其余内容与“数字电路模拟程序-1”相同。

电路元件 电路中包含与门、或门、非门、异或门、同或门、三态门、译码器、数据选择器、数据分配器九种元件。元件特征如下:

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

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

数据选择器:数据选择器的作用是从多路输入信号中选择一个,并将其信号直接送往唯一的输出端,选择哪一路输入信号由控制端决定。 根据输入引脚数量的不同有二选一数据选择器(1个控制端)、四选一数据选择器(2个控制端)、八选一数据选择器(3个控制端)等

数据分配器:数据分配器的作用与数据选择器正好相反,是将唯一的一路输入信号输出到多路输出引脚的其中之一,选择哪一路输出引脚输出由控制端决定。 根据输出引脚数量的不同有二路数据分配器(1个控制端)、四路数据分配器(2个控制端)、八路数据分配器(3个控制端)等 2、程序输入 1)

元件信息: 用A、O、N、X、Y、S 、M、Z、F分别用作 与门、或门、非门、异或门、同或门、 三态门、译码器、数据选择器、数据分配器九种元件的元件标识符。 电路中的每个与门、或门用“标识符(输入引脚数)+编号”作为其元件名。 例如:A(8)1表示一个8输入引脚的与门,O(4)2代表一个4输入引脚的或门。 电路中的每个非门、异或门、同或门用“标识符+编号”作为其元件名。

电路中的数据选择器、数据分配器用“标识符(控制引脚数)+编号”作为其元件名。 例如:Z(2)2代表一个四选一数据选择器,F(3)2代表一个8路数据分配器。

 每个元件的输入引脚连续编号。假设元件有n个输入引脚,则其编号取值范围为\[1,n\],且引脚号不重复。本题涉及的五种元件都只有一个输出引脚,输出引脚号默认为0。

实现方式

多态器件计算、多轮信号传播、多格式输出

1. 整体架构

单层电路,继承程序 1 全部 5 门,新增三态门 S、译码器 M、数据选择器 Z、数据分配器 F 共 4 类器件;支持控制引脚、多输出、高阻无效态、差异化输出格式。

2. 数据存储结构

  1. 内部静态Gate实体类:封装元件类型字符、括号参数、元件编号;
  2. allGate:key = 元件名,value=Gate 对象,统一管理 9 类器件;
  3. inPort:顶层全局输入信号;
  4. link:连线映射,源引脚→目标引脚数组;
  5. pinVal:引脚状态池,存储 Integer (0/1 有效) 或字符串(译码 / 分配器专用输出)。

3. 输入解析实现

  1. 逐行解析至end结束;
  2. INPUT:解析全局外部输入;
  3. [ ]连线块拆分源引脚、目标引脚数组,遍历所有引脚自动调用createGateByPin生成 Gate 对象;
  4. Gate 构造逻辑区分 9 类元件命名规则:
    • A/O/M/Z/F:带(参数)格式,提取括号内参数 + 末尾编号;
    • N/X/Y/S:无括号,直接截取末尾数字作为编号。

4. 电平传播与器件计算

  1. 多轮信号扩散:最多循环 20 轮传递电平,直到无引脚更新,解决多器件依赖时序;
  2. 分类型独立计算函数calcAnd/calcOr/calcNot/calcXor/calcXnor/calcTri/calcDec/calcMux/calcDemux:
    • 基础门:同程序 1 逻辑;
    • S 三态门:0 号控制引脚为 1 时,输入引脚 1 电平直通输出引脚 2;控制无效则不生成输出;
    • M 译码器:校验 S1=1、S2+S3=0,读取输入引脚二进制编码,缓存输出编码值;
    • Z 数据选择器:读取控制引脚组合,选中对应数据输入,写入唯一输出引脚;
    • F 数据分配器:读取控制组合,仅对应输出通道保留电平,其余填充-无效符号,拼接字符串存储。

5. 差异化输出逻辑

  1. 固定输出顺序:A→O→N→X→Y→S→M→Z→F;同类器件按编号升序;
  2. 分类型定制输出格式:
    • 基础门、三态、选择器:元件名-输出引脚:电平;
    • 译码器:元件名:输出为0的引脚编号;
    • 数据分配器:元件名:--0-形式字符串;
  3. 无有效输出的器件直接跳过不打印。

代码规模

屏幕截图 2026-06-24 152304

屏幕截图 2026-06-24 152317

屏幕截图 2026-06-24 152342

可以看出,calcAllGate()复杂度 12、31 行,统一调度 9 类器件运算;createGateByPin()复杂度 8、45 行,解析 9 类元件命名、创建 Gate 对象;各类器件独立计算函数calcAnd/calcDec/calcMux/calcDemux单方法复杂度 4~7,分支拆分清晰;main()复杂度仅 3,仅做流程调度,不再承载全部业务逻辑。语句集中在深度 2、3 层级,深度 6 仅少量代码,嵌套层级比第一次更浅。

第六次作业

作业要求

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、#子电路信息可以将一部分电路设定为一个子电路,子电路可以在主电路中被引用。子电路的输入按顺序包括以下几部分:

子电路起始信息:C+子电路编号+:

子电路输入信息: INPUT:输入1+英文空格+输入2+英文空格+....+英文空格+输入n,

子电路输出信息: OUT:输出1+英文空格+输出2+英文空格+....+英文空格+输出n,

子电路连接信息:(格式与主电路输入信息相同),子电路的元件编号与主电路中的编号可能相同。

子电路结束标志:endc

输入、输出信息的编码可以作为子电路的引脚号在主电路中引用。

子电路元件输出时带上子电路的编号,格式为:子电路编号+“-”+元件编号+“-”+引脚号+“:”+引脚输出 例如:定义一个编号2的子电路,包含A、B两个输入和一个输出O: C2: INPUT:A B OUT:C [A X1-1] [B X1-2] [X1-0 Y1-1] [A Y1-2] [Y1-0 C] endc 主电路中的运用: INPUT: A-1 B-1 [A C2-A] [B C2-B] [C2-C N1-1] [N1-0 OUT] end 输出信息: C2-X1-0:0 C2-Y1-0:0 N1-0 :1 所有子电路信息都在主电路信息之前定义完成。

6、#异常输入信息 当出现各类异常输入情况时,电路应输出相应提示,异常情况包括:

  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 注:如果一条输入出现了多种异常,按以上异常先后顺序为优先级,顺序靠前者优先级越高,最后输出优先级最高的异常输出。 当电路输入信息中多条输入都包含异常,只处理排在最前面的异常信息。

7、输入结束信息 所有输入以end为结束标志,end之后出现的内容忽略不计

8、程序输出 按照与门、或门、非门、异或门、同或门的顺序依次输出所有元件的输出引脚电平。同类元件按编号从小到大的顺序排序。 如果某个元件的引脚没有接有效输入,元件输出无法计算,程序输出结果忽略该元件

9、测试输入默认满足以下条件: 1)每个元件的输入引脚连续编号。假设元件有n个输入引脚,则其编号取值范围为[1,n],且引脚号不重复。 2)本题涉及的五种元件都只有一个输出引脚,输出引脚号默认为0。 

实现方式

组合模式、语法校验器、分层独立仿真、冲突检测

1. 整体架构

组合模式分层架构,分为顶层主电路 + 多个独立子电路 SubCircuit;继承程序 1 基础 5 门,新增子电路定义、跨层端口引用、6 级优先级连线异常检测。

2. 数据存储结构分层

全局 Main 层

  1. subCircuitMap:TreeMap 存储所有子电路(子电路编号→SubCircuit 实例);
  2. signalMap:全局统一电平池,所有顶层引脚、子电路门输出统一存入;
  3. pinDriver:记录每个输入引脚唯一驱动源,用于冲突检测;
  4. connectLines:缓存顶层所有连线语句原文,用于异常打印。

SubCircuit 子电路类

  1. 子电路 ID、对外输入端口列表、对外输出端口列表;
  2. 内部连线集合、子电路端口局部电平缓存;
  3. gateOutputs:存储子电路内部所有门的输出引脚,用于统一汇总输出。

3. 输入解析流程

  1. 预读取全部输入行,先循环解析所有子电路(以C编号开头,endc结束);
    • 子电路内部解析:INPUT:对外输入端口、OUT:对外输出端口、内部[ ]连线;
  2. 解析顶层全局INPUT:外部输入信号;
  3. 逐行解析顶层[ ]连线,调用checkConnectError做优先级异常校验,第一条异常直接终止程序

4. 6 级优先级异常检测实现

按优先级依次校验,命中即返回错误字符串:
  1. 连线包含多个输出源 → more than one input;
  2. 无目标输入引脚 → include none input;
  3. 无输出源引脚 → include none output;
  4. 输入输出引脚顺序颠倒 → sequence error;
  5. 同一输入引脚被多个源驱动 → input signal conflict;
     
    校验通过后记录引脚驱动映射,冲突提前拦截。

5. 分层电路仿真计算

  1. 计算顺序:先批量计算所有子电路内部逻辑,再处理顶层电路信号;
  2. 子电路独立计算:每个子电路拥有局部电平池,内部连线信号传递,收集内部所有门输出并同步到全局电平池;
  3. 跨层信号传递:顶层连线若目标为C子电路号-端口,更新子电路端口电平并重新计算子电路内部;
  4. 门运算逻辑复用calcGateLogic,和程序 1 基础门逻辑完全一致;
  5. 迭代循环更新电平,直到无信号变化。

6. 分层输出规则

  1. 输出顺序:与→或→非→异或→同或;同类元件按编号升序;
  2. 区分子电路内部元件与顶层元件:
    • 子电路门输出格式:C子电路编号-元件输出引脚:电平;
    • 顶层门输出:元件名-0:电平;
  3. 输入缺失、无有效电平的元件全部忽略不输出。

代码规模

屏幕截图 2026-06-24 153037

屏幕截图 2026-06-24 153050

屏幕截图 2026-06-24 153112

屏幕截图 2026-06-24 153138

可以看出,全部业务耦合在单一 Main 类仅 1 个业务类,没有拆分解析器、仿真器、输出工具、引脚工具,所有工具函数、校验逻辑、仿真逻辑平铺在 Main 中。分支语句占比 30.5%,大量硬编码判断,checkConnectError线性堆叠 5 类异常判断,calcGateLogic多路分支判断 5 种基础门运算,引脚类型、子电路端口、全局信号重复 if 判断,无策略 / 多态封装。

踩坑心得

踩坑点:A(8)1带括号、X5无括号,最开始只用一套正则解析,要么提取不到输入引脚数,要么提取编号报错;直接用split硬分割容易数组越界。

解决心得:分两套正则匹配,先判断是否包含(,再分别提取参数与末尾编号;工具方法统一封装提取数字,不要重复写正则

踩坑点:直接用split(" "),多个空格会切出空字符串,后续split("-")数组长度不足抛出数组越界。

心得:分割字符串统一使用split("\\s+")匹配任意空白,过滤空片段

踩坑点:正则提取末尾数字时,门名称含多段数字,错误提取括号内数字作为排序编号。

心得:正则只捕获字符串最后一段数字,区分括号参数与元件编号

踩坑点:C2-A识别逻辑混乱,分不清子电路输出端口(可作为信号源)、输入端口(不能作为源),异常校验误判。

心得:单独工具方法区分全局信号、门输入 / 输出引脚、子电路输入 / 输出端口,做分类判断。

踩坑点:多条连线指向同一个目标引脚,未记录驱动源,不会抛出input signal conflict。

心得:用 Map 记录每个输入引脚唯一驱动源,每次连线先校验是否已存在驱动。

还有一些代码逻辑错误,涉及的步骤较多,就不一一展示了。

改进建议

第4次作业

main 方法圈复杂度 42、110 行语句,读取输入、解析 INPUT、解析连线,元件命名解析、全局信号分发,迭代循环仿真所有门、布尔运算,分类收集门、排序、格式化输出,所有流程串行堆叠在 main,多层循环嵌套到 7 层,维护、修改成本极高,应该要将Main拆分为输入解析、信号仿真、结果输出三大模块,main 仅做调度,把复杂度从 42 降到 5 以内,以And/Or/Not/Xor/Xnor 作为子类,用多态替代computeOutput内超长 if 分支。

第5次作业

Main方法太笨重,将 calcAnd/calcDec 等逻辑移入 Gate 子类,消除 Main 中大量独立 calc 方法,拆分InputParser、CircuitSimulator、ResultPrinter,把 Main 的解析、仿真、输出职责分出去,区分 0/1 / 高阻,消除 Object 混用类型,将在后续的复习题集尝试实现。

第6次作业

拆解超高复杂度 main () 方法,拆分 4 个独立业务类,完全剥离 main 方法职责:

InputFileParser:只负责读取文本、解析子电路、解析全局 INPUT、解析连线;

ConnectionErrorChecker:异常校验模块,用策略模式封装 5 类异常,移除 main 内超长 if 判断;

CircuitSimulator:承载所有仿真逻辑(子电路批量计算、信号传播、门电平运算);

ResultPrinter:收集所有有效门、按类型编号排序、分层格式化输出;

改造后 main 仅做流程调度,代码压缩至 20 行以内,尽量把复杂度降至 < 5。

给 SubCircuit 增加业务方法,解除与 Main 双向依赖,所有子电路自身解析、自身计算,Main 仅调用对外接口。5 种门作为子类,calcGateLogic多态实现,移除 switch/if 分支,后续将在复习题集里尝试实现。

总结

我看下来,这三次作业暴露的缺陷有:

  1. 作业 4:巨型 main 方法嵌套过深、元件编号正则提取错误、无面向对象封装;
  2. 作业 5:器件引脚分区记混、译码 / 多路选择控制位高低位颠倒、高阻无效状态忘记过滤;
  3. 作业 6:异常校验优先级颠倒、子电路解析指针偏移、分层信号读写冲突、输入输出源判断逻辑写反。
  4. 全部逻辑过度耦合在 Main 主类,未拆分为解析器、仿真器、输出工具三层;
  5. 缺少统一枚举封装(引脚状态、元件类型、异常类型),大量 if/switch 分支;
  6. 大量重复工具逻辑(引脚拆分、编号提取、类型判断)无独立工具类;

并且后续的课堂学习让我明白了这三次作业最严重的错误就是违背了面向对象程序设计的开闭原则,即对扩展开放,对修改关闭,而我的代码没有遵从这一原则,我将在复习题集里对这一点进行重点练习。

三次作业让我学到的东西如下:

  1. 知识层面这三道题完整覆盖数字组合逻辑电路理论:基础门、三态开关、译码器、多路选择 / 分配器、模块化子电路;同时覆盖软件编程能力:文本解析、数据结构、面向对象、分层架构、错误校验、多态设计。
  2. 工程层面这三次作业呈现完整软件迭代流程:最小可用版本→功能扩展版本→架构重构 + 容错完善版本,直观地让我看到 “先实现功能,再扩展硬件,最后优化架构、提升程序健壮性” 的标准开发流程。
  3. 个人成长层面我能在题目里感受到,从最初纯面向过程、不考虑扩展性的简易脚本,逐步学习封装、分层、模块化、异常处理,理解单一职责、组合模式、策略模式等设计思想,认识到前期架构设计对后期扩展、调试、排错的决定性作用。
  4. 不足反思三份代码均存在前期架构设计不足的问题,是我在做的过程中比较急躁,没有太多思考,随需求叠加不断堆砌分支与逻辑,导致 main 方法复杂度持续升高;若初期做好分层、抽象、工具类封装,可大幅减少后期重构工作量,也能规避绝大多数解析、仿真、校验类 bug。
posted @ 2026-06-24 18:24  neya  阅读(3)  评论(0)    收藏  举报