马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
前言
本阶段的三次作业围绕“数字电路模仿步调”睁开,同1,2,3,仍旧是一个典范的迭代式开发项目。
这三次作业都比前三次难,而难告急是在逻辑与细节之中,让大部门人难以到达满分而只能到达高分。
以下是三代数字电路模仿步调的扼要分析。涵盖了知识点与难度分析,难度评级。
| 数字电路模仿步调 | 知识点、重点分析 | 难度评级 | 一代
| 底子门电路(与、或、非、异或、同或)的逻辑模仿,重点在于类的计划和信号的单向传播。 | 底子 | | 二代 | 引入复杂组合逻辑器件(三态门、译码器、数据选择器、数据分配器),重点在于多引脚管理(控制/输入/输出引脚分离)和多值逻辑(高阻态Z)。 | 较难(复杂度飙升↑↑) | | 三代 | 引入子电路(模块化)和非常处理处罚,重点在于条理化计划和错误检测。 | 难(模块嵌套) | 计划与分析
第一次作业
第一次作业共包罗三个核心类:Gate(门电路类)、Source(信号源类)、Main(主类)。
本次作业接纳面向过程的数据结构与单向信号传播机制,结构简单直白。此中Gate类为实体类,存储元件名称、范例、输入引脚数、编号、输出值以及各引脚的信号泉源映射;Source类作为辅助记载类,用于存储信号泉源的元件名和引脚号;Main类负责数据输入、元件创建、信号传播和结果输出。
类图
依赖关系:本次作业类之间依赖关系简单——Main类依赖Gate、Source以及聚集容器、Gate类依赖Source作为引脚信号泉源的映射值、Source类无任何依赖,属于独立辅助记载类。
复杂度分析
可以看到,复杂度怎么看都是有点爆的,这不但是标题自己复杂性所致。
而且是代码规范性题目:
Main部门用大量 if-else 硬编码实现差别门电路(A/O/N/X/Y)的逻辑,每新增一种门范例都要修改这个方法,违反开闭原则。
每个分支里都重复了 “获取输入值→判空→盘算结果” 的模板代码,冗余严肃。
直接依赖 getVal 方法获取输入值,和外部状态耦合,难以单独测试。
用正则 + 多分支判断门电路范例,同时处理处罚两种差别的定名格式(A(2)1 和 N1 等),逻辑复杂。包罗大量格式校验和非常捕捉,嵌套层级多,可读性差。
固然,也肯定有可以优化之处:
如拆分main方法依照, 单一职责重构 calc 方法;用战略模式消除 if-else;优化化 create 方法的逻辑等...
下为依照单一职责示例:
查察代码- // 1. 读取输入
- List<String> conns = readInput(sc);
- // 2. 解析连接关系并创建门电路
- parseConnections(conns);
- // 3. 迭代计算输出值
- computeOutputs();
- // 4. 按顺序输出结果
- printOutputs();
复制代码 小结(一)
本次作业能拿100分是比力容易的,固然main复杂度堆起来了,但大要可有可无。
针对自界说对象存入哈希聚集的需求,按照规范重写equals()与hashCode()方法,包管聚集可以大概正确判断对象相当性,是面向对象开发中容器利用的告急实践。步调借助HashMap、List等聚集类,同一管理大量逻辑门与输入信号,实现对象的批量构造与快速查找。
本次作业不但完成了逻辑电路模仿的标题要求,也让我踏实把握了类与对象、封装、聚集框架、方法计划等核心知识点,学会用面向对象头脑拆解现实题目、搭建步调结构。
第二次作业
第二次作业在第一次的底子上,新增了五类复杂器件:三态门(S)、译码器(M)、数据选择器(Z)、数据分配器(F),同时优化了信号传播机制。
本次作业共包罗:Device抽象类及其五个子类(AndDevice、OrDevice、NotDevice、XorDevice、XnorDevice),以及专门处理处罚复杂器件的独立类(TriDevice、DecDevice、MuxDevice、DemuxDevice)。别的引入了pinSignals全局信号池,实现引脚级别的信号存储与传播。
计划厘革:
- 从第一次的Gate单一实体类,演变为抽象基类+具体子类的继续体系
- 引入pinSignals(Map)作为全局信号池,更换了Gate.output的单一值存储
- 信号传播从calc方法的递归求值,演变为propagateSignals的迭代传播机制
类图
依赖关系:Main类依赖Device体系及pinSignals信号池;Device为抽象基类,五个子类(AndDevice、OrDevice、NotDevice、XorDevice、XnorDevice)各自独立实现calculate方法;复杂器件(TriDevice、DecDevice、MuxDevice、DemuxDevice)的calculate返回null,其逻辑在Main.propagateSignals中硬编码处理处罚,pinSignals作为全局信号池被Main和evaluateDevice共同依赖。本次作业类之间依赖关系适中。
复杂度分析
与第一次对比,均匀圈复杂度从7.00降至5.58,得益于职责拆分更加过细,方法数从11增至26。但最大圈复杂度从28升至40,阐明个别方法负担了过重的职责,成为新的复杂度热门。
第二次作业在复杂度管理上有显着优化:均匀圈复杂度从10.18降至5.58,方法数从11增至26,职责拆分更加过细。但复杂器件(S/M/Z/F)未纳入继续体系,导致evaluateDevice方法成为新的"天主方法",圈复杂度高达40,认知复杂度77,远超第一次的calc方法(v(G)=24)。
别的,printOutputs也因九种器件输特别式各异而到达v(G)=24。两个方法合计贡献了64的圈复杂度,占全文件145的44%。假如继续相沿这种计划,第三次作业新增子电路功能时,这两个方法将进一步膨胀,终极导致架构失控。根本办理方案是将S/M/Z/F也纳入Device继续体系,各子类独立实现calculate和output方法,消除硬编码分支。
小结(二)
通过本次作业,我加深了对继续与多态的明白。五种底子门通过继续Device分别实现calculate方法,代码结构比第一次更加清楚。pinSignals全局信号池的引入也有效办理了多引脚器件的信号存储与读取题目。
优化方向可以将S/M/Z/F也纳入Device继续体系,各子类独立实现calculate和output方法,消除evaluateDevice和printOutputs中的硬编码分支。同时废弃Gate体系,同一信号存储为pinSignals单一信号池,制止双重路径题目。
第三次作业
第三次作业在前两次的底子上,新增了两大核心功能:子电路(模块化) 和非常输入检测。
本次作业共包罗:BlockModule(模块类)、LogicUnit抽象类及其五个子类(AndUnit、OrUnit、NotUnit、XorUnit、XnorUnit)、WireLink(线网类),以及Main类中大量的静态剖析方法。别的,第一次的Gate体系、第二次的Device体系与第三次新增的LogicUnit体系三套元件表现同时共存。
类图
依赖关系:Main类依赖BlockModule、LogicUnit、WireLink以及前两次遗留的Gate/Device体系;BlockModule包罗members(模块内元件映射)和internalLinks(内部线网列表),递归剖析子电路界说;LogicUnit为抽象基类,五个子类各自实现compute方法;WireLink作为独立线网记载类,存储驱动端和吸收端信息。本次作业类之间依赖关系急剧增长,且三套元件体系功能重叠,导致架构杂乱。
复杂度分析
第三次作业最大圈复杂度虽从40降至22,但复杂方法数量从4个增至5个,parseMain(v(G)=22)和runSim(v(G)=19)成为新的复杂度热门。runSim的认知复杂度高达81,是全文件最难明白的方法。根本题目在于三套元件体系(Gate/Device/LogicUnit)共存,导致信号存储存在三重路径,子电路克隆时内部映射丢失。parseMain负担了四个独立职责,严肃违反单一职责原则。偶然间做题就是图方便难免违反。
改进思绪:废弃Gate和Device体系,同一为LogicUnit一套体系;子电路接纳定名空间重定名而非克隆;将parseMain拆分为剖析、创建、毗连、验证四个独立方法。
下为删除 cloneUnit 方法 + 改造 registerUnitByPin
查察代码- static void registerUnitByPin(String token) {
- // 子电路模块:Cxxx-端口/器件
- if (token.startsWith("C") && token.contains("-")) {
- int dash = token.indexOf('-');
- String modTag = token.substring(0, dash);
- String innerName = token.substring(dash + 1);
- BlockModule mod = moduleLib.get(modTag);
- if (mod == null) return;
- // 遍历模块内部所有逻辑单元,拼接 模块名-单元名 作为全局命名空间
- for (Map.Entry<String, LogicUnit> e : mod.members.entrySet()) {
- String globalUnitName = modTag + "-" + e.getKey();
- // 命名空间全局唯一,不存在则新建,不再克隆
- if (!allUnits.containsKey(globalUnitName)) {
- // 直接基于原始定义创建,使用命名空间名称
- LogicUnit newUnit = makeUnit(globalUnitName);
- if (newUnit != null) {
- allUnits.put(globalUnitName, newUnit);
- }
- }
- }
- // 模块内部连线,同样追加命名空间,加入全局连线表
- for (WireLink wl : mod.internalLinks) {
- boolean exists = false;
- for (WireLink existing : allLinks) {
- if (existing.driver.equals(wl.driver) && existing.receiver.equals(wl.receiver)) {
- exists = true;
- break;
- }
- }
- if (!exists) {
- allLinks.add(new WireLink(wl.driver, wl.receiver));
- }
- }
- }
- // 普通独立逻辑门
- else if (token.contains("-")) {
- String unitName = token.substring(0, token.lastIndexOf('-'));
- if (!allUnits.containsKey(unitName)) {
- LogicUnit unit = makeUnit(unitName);
- if (unit != null) allUnits.put(unitName, unit);
- }
- }
- }
复制代码 小结(三)
通过本次作业,我对模块化计划有了更深刻的明白,第三次作业也是这三次中最重量级也最告急的一个。子电路的本质不是简单的类嵌套,而是定名空隔断离——将一组元件封装为独立单元,对外只袒露输入输出端口,内部信号对外部不可见。这让我遐想到操纵体系中的历程隔离概念,固然层级差别,但头脑相通。非常检测机制的引入也让我熟悉到,在复杂体系中输入验证不是可有可无的附属品,而是包管体系坚固性的第一道防线,一个计划良好的体系应该在错误发生时给出清楚的提示,而非静默失败。
本次作业已完成大部门测试点,能想到的输入根本测试过无题目,但仍没有满分,还是有细节待究。
采坑心得
第一次作业踩坑记载
坑一:忽略引脚0的限定
第一次作业中,getVal方法没有s.pin == 0判断,导致读取信号时大概取到输入引脚而非输出引脚。
当上层元件毗连到X1-1(异或门的输入引脚)时,getVal会返回g.output,但此时输出引脚还没盘算完,大概返回的是错误的信号值。
查察代码- static int getVal(Source s) {
- if (inputs.containsKey(s.dev)) return inputs.get(s.dev);
- Gate g = gates.get(s.dev);
- if (g != null) return g.output; // ❌ 没有检查pin是否为0
- return -1;
- }
- //修正↓
- static int getVal(Source s) {
- if (inputs.containsKey(s.dev)) return inputs.get(s.dev);
- Gate g = gates.get(s.dev);
- if (g != null && s.pin == 0) return g.output; // ✅ 只读输出引脚
- return -1;
- }
复制代码 第二次作业踩坑记载
坑一:第一次的Gate.output和第二次的pinSignals同时存在,读取和写入走差别路径。
可以废弃Gate.output,同一从pinSignals读取:
查察代码- static int getVal(Source s) {
- if (inputs.containsKey(s.dev)) return inputs.get(s.dev);
- Gate g = gates.get(s.dev);
- if (g != null && s.pin == 0) return g.output; // ❌ 仍从Gate读
- return -1;
- }
- static void propagateSignals() {
- // ...
- pinSignals.put(name + "-0", result); // 写到pinSignals
- }
- //修正↓
- static int getSignal(String pinName) {
- return pinSignals.getOrDefault(pinName, -1);
- }
复制代码 坑二:evaluateDevice中过早返回
evaluateDevice在查抄控制引脚时,一个引脚缺失就直接return false,但其他引脚大概还没传播到。
控制引脚和数据引脚同时传播时,数据先到控制后到,由于控制缺失直接返回false,导致同一轮次内无法完成求值。
应该全部引脚读取完再同一判断:
查察代码- Integer ctrl = pinSignals.get(name + "-0");
- if (ctrl == null) return false; // ❌ 过早返回
- Integer in = pinSignals.get(name + "-1");
- if (in == null) return false;
- // ...
- //修正↓
- Integer ctrl = pinSignals.get(name + "-0");
- Integer in = pinSignals.get(name + "-1");
- if (ctrl == null || in == null) return false; // ✅ 都读完再判断
复制代码 第三次作业踩坑记载
坑一:第一次的Gate、第二次的Device、第三次的LogicUnit同时存在,一个元件大概出如今三个Map里。
registerUnitByPin遇到子电路引脚时,先克隆LogicUnit,但runSim中又从Device体系读取信号,导致信号断裂。
可以废弃前两套体系,只用LogicUnit一套。
查察代码- // 第一次
- Map<String, Gate> gates = new HashMap<>();
- // 第二次
- Map<String, Device> devices = new HashMap<>();
- // 第三次
- Map<String, LogicUnit> allUnits = new HashMap<>();
- //修正↓
- LogicUnit clone = cloneUnit(e.getValue(), fullName);
- allUnits.put(fullName, clone);
复制代码 坑二:非常检测过于激进
某些正常毗连格式(如多个输出引脚毗连到同一个输入引脚?标题说一个输入引脚不能毗连多个输出引脚,但反过来是可以的)被误判为错误。->原来正确的Case由于非常检测误判而输堕落误信息。
要细致对照标题要求,只检测明白克制的环境(没办法只能猜,猜也猜不到满分😀)
查察代码- if (outputCount > 1) {
- faultMsg = "ERROR: " + line + " include more than one input";
- hasFault = true;
- return;
- }
复制代码
踩坑小结
- 能跑通的代码不愿定对,引脚0的坑就是血的教导
- 信号源只能有一处,多路径肯定杂乱
- 不要打补丁,要重构——欠下的技能债连本带利都要还
- 以样例为准,标题形貌与样例辩论时信样例
- 一个方法只做一件事,parseMain和calc就是反面讲义
(PS.最大的坑是无法明白标题要求。在第一次数字电路作业中,标题看似写的规则非常清楚,实则暗藏玄机,示例中的OUT是什么?0是那边来的?原来OUT不是很有所谓,0是默认👴等等,满是看示例自己猜,自己探索,自己去想,自己去试试是不是如许的,背面的一些测试点更是如许,找非常逻辑非常示例,本次作业比上三次作业最强横之处在于测试点更为阴间,能想到的点都能过,大概是一些答案与出题者想的有所收支?本次作业想做到全对耗费的时间无疑是巨大的,我也没有做到满分的地步,做到高分应该算是很好的了)
改进发起
一、代码结构改进
题目: 三次作业的方法圈复杂度连续超标,Main.calc(v(G)=24)、Main.evaluateDevice(v(G)=40)、Main.parseMain(v(G)=22)都是典范的"天主方法"。
发起: 严格依照单一职责原则,每个方法只做一件事。以parseMain为例,拆分为parseInputLines、parseConnections、validateConnections、buildNetwork四个方法,各自独立测试。
题目: 三套元件体系(Gate/Device/LogicUnit)共存,信号存储存在多重路径。
发起: 只保存一套元件体系,同一信号源。若从第三次作业重构,应废弃Gate和Device,只保存LogicUnit,全部信号通过唯一的sigMap存取。
二、架构计划改进
题目: 子电路接纳克隆物理元件的方式实现,导致内部信号映射丢失。
发起: 接纳定名空间重定名。子电路实例化时,给其内部全部信号名加上前缀(如C1.And1-0),信号池同一维护,无需克隆。
题目: 复杂器件(S/M/Z/F)未纳入继续体系,在evaluateDevice中硬编码处理处罚。
发起: 将S/M/Z/F也纳入Device继续体系,各子类独立实现calculate和output方法,消除硬编码分支。
三、可扩展性改进
题目: 新增器件范例必要修改create、calc、evaluateDevice、printOutputs等多个方法。
发起: 引入工厂模式创建元件,战略模式处理处罚盘算,新增器件只需新增一个子类,无需修改已有代码。
题目:引脚号盘算分散在各处(数据选择器输出引脚、译码器输出起始引脚等)。
发起: 在Device基类中界说getOutputPin()和getInputPins()等方法,由各子类自行盘算,外部只需调用接口。
四、非常处理处罚改进
题目: 非常检测逻辑与剖析逻辑混在parseMain中,圈复杂度22。
发起: 独立的InputValidator类负责全部非常检测,剖析完成后同一校验,职责清楚。
题目: 错误信息不敷明白,难以定位题目。
发起: 非常发生时输出具体的错误位置和渴望格式,如ERROR: Pin X1-1 not connected,而非笼统的ERROR: invalid input。
总结
三次迭代作业做下来,最大的感受就是:能跑和好改是两码事。
一、代码结构改进
题目: 三次作业的方法圈复杂度连续超标,Main.calc(v(G)=24)、Main.evaluateDevice(v(G)=40)、Main.parseMain(v(G)=22)都是典范的"天主方法"。
发起: 严格依照单一职责原则,每个方法只做一件事。以parseMain为例,拆分为parseInputLines、parseConnections、validateConnections、buildNetwork四个方法,各自独立测试。
题目: 三套元件体系(Gate/Device/LogicUnit)共存,信号存储存在多重路径。
发起: 只保存一套元件体系,同一信号源。若从第三次作业重构,应废弃Gate和Device,只保存LogicUnit,全部信号通过唯一的sigMap存取。
二、架构计划改进
题目: 子电路接纳克隆物理元件的方式实现,导致内部信号映射丢失。
发起: 接纳定名空间重定名。子电路实例化时,给其内部全部信号名加上前缀(如C1.And1-0),信号池同一维护,无需克隆。
题目: 复杂器件(S/M/Z/F)未纳入继续体系,在evaluateDevice中硬编码处理处罚。
发起: 将S/M/Z/F也纳入Device继续体系,各子类独立实现calculate和output方法,消除硬编码分支。
三、可扩展性改进
题目: 新增器件范例必要修改create、calc、evaluateDevice、printOutputs等多个方法。
发起: 引入工厂模式创建元件,战略模式处理处罚盘算,新增器件只需新增一个子类,无需修改已有代码。
题目: 引脚号盘算分散在各处(数据选择器输出引脚、译码器输出起始引脚等)。
发起: 在Device基类中界说getOutputPin()和getInputPins()等方法,由各子类自行盘算,外部只需调用接口。
四、非常处理处罚改进
题目: 非常检测逻辑与剖析逻辑混在parseMain中,圈复杂度22。
发起: 独立的InputValidator类负责全部非常检测,剖析完成后同一校验,职责清楚。
题目: 错误信息不敷明白,难以定位题目。
发起: 非常发生时输出具体的错误位置和渴望格式,如ERROR: Pin X1-1 not connected,而非笼统的ERROR: invalid input。
劳绩层面
这三次作业劳绩良多。第一次让我真正明白了什么叫"数据和举动封装在一起",第二次加深了对继续和多态的明白,第三次固然分数不快意,但让我明白了模块化计划的核心是定名空隔断离,不是复制粘贴。三个版本的对比摆在面前,对我java面向对象的提拔无疑是巨大的。锻炼我调试程度,进步了抗压程度,分数也迫使自己有了研讨的耐心与动力,这三次作业还为自己巩固了上课所学的知识点,更是进步了实践本领,可以说三次作业的代码量都不少,题黑白常好的题,既有逻辑思索环节,也有细节探寻环节,也有代码构思环节。我还是以为这三次作业告急是明白,然后去编程去实现,对逻辑的表现黑白常深刻的,对各个板块的分别和算法的讲求也表现的非常的告急,换输出、转换算法,相当于换了一个思绪,很多时间思量的不全的话算法对测试点的正确率也是天壤之别的,想出所能及的最好的思绪,选择最优的算法,是解这种题的最好方法,告急是耐心,岑寂,然后逐个击破难点!
|