BlogWork(3)-All In One(总结)

一场关于设计的思辨之旅

回望整个学期的面向对象课程,它为我带来的并非技术栈的深度挑战,而是一场由始至终围绕“软件设计”展开的思辨之旅。在此之前,我通过自学已具备一定的Java基础,但这门课程的价值,恰恰在于它引导我超越了语法的层面,进入了一个更广阔的思考空间——在这里,我们探讨的不再是代码能否运行,而是系统的可维护性与设计的合理性。
这篇Blog,便是我在这趟旅程中,围绕几个核心认知主题的复盘与沉淀。

在混沌中探寻秩序

本课程提供的一个核心思考空间,便是通过“电梯调度”系列作业,将我们置于一个充满不确定性的设计场景中。其题干在调度算法上刻意保留了大量的模糊地带,这并非是题目本身的缺陷,而是一个精心设计的开放性问题,迫使我们思考:当需求本身存在高度不确定性时,该如何构建一个健壮、灵活的系统?
我最初的尝试是“暴力试探”——试图通过排列组合各种可能的调度策略,去猜测既定的标准答案。这直接导致了第一次作业中那个圈复杂度极高的selectNextTargetBasedOnHeads()函数。它虽然通过了测试,但却是一个复杂度过高且难以维护的模块。
这次经历带来的反思是深刻的:

  • 软件设计并非标准解题:在许多场景下,它不是寻找唯一正确的答案,而是在约束下进行权衡与创作。
  • 面向变化而设计:面对不确定性,设计的核心应该是拥抱变化。与其将所有逻辑硬编码,不如将其拆分为多个职责单一的策略,用清晰的状态机去驱动。这样,当需求逐渐明朗或发生变更时,系统才能从容应对。

最终,在第二次作业中对这份高度耦合的代码进行彻底重构,让我在第三次作业中体会到了回报:不到10分钟,便优雅地完成了核心需求变更。这是我对“设计先行”理念最直观的一次体验。

从MVC到DDD的架构演进

如果说“电梯调度”是关于如何应对“未知”,那么“航空订单管理”系列作业,则是关于如何优雅地处理“已知”的业务复杂度。这个项目算法相对简单,从而将我们的全部精力都解放出来,投入到一个纯粹的设计实践环境中。

初次尝试:简化的MVC架构与职责分离

在第一次作业中,面对一个典型的包含逻辑、数据表示和数据显示的场景,我自然而然地想到了解耦这三部分。因此,我尝试使用了业界主流的MVC架构。虽然我并未严格遵循其全部规范,甚至是一个简陋的实现,但其核心思想——职责分离面向接口编程——贯穿了始终。Reader负责输入(可视为C),Service负责业务(M),VOWriter负责输出(V)。
这个初步的MVC架构并非失败的设计,它已经具备了一定的优点:

  • 结构清晰:相比将所有代码写在一起,系统在宏观上有了基本的层次划分。
  • 实现了初步解耦:输入/输出与核心业务逻辑被分离开,具备了一定的可维护性。

然而,在实践中我也意识到了它的局限性:核心的业务逻辑,如计算货物的计费重量、运输费用等,全部集中在FlightServiceImpl中。这使得数据实体类(ClientDataEntity, GoodsEntity)沦为了仅有get/set方法的“贫血模型”,而Service层则有向“胖脚本”演变的趋势。

架构精进:向领域驱动设计(DDD)的转向

当第二次作业增加新需求时,我面临一个选择:是在现有的Service层上继续叠加逻辑,还是寻求一个更优的架构?我选择了后者。在意识到现有代码问题的同时,我恰好对DDD(领域驱动设计)有了一些初步了解,便决定以此为契机,进行一次架构的精进与实践。
这次重构的目标非常明确:将分散在Service层的业务逻辑,回归到它最所属的领域对象中,构建一个真正意义上的、富含业务智慧的领域模型。

破局之道:引入“充血模型”的实践细节

我的探索过程如下:

  • 逻辑的重新分配:我做的第一步,就是将原先位于VO层和Service层的计算逻辑剥离出来,准备赋能给新的领域对象。这使得各层职责更加清晰,VO层得以回归其作为纯粹展示数据载体的本质。
  • 创建包含行为的领域对象(充血模型):我分析业务,识别出核心的领域概念,如“客户”、“货物”,并创建了AbstractClientDomainAbstractGoodsDomain作为它们的抽象。最关键的一步是,我将业务行为封装进这些领域对象。例如,不同类型的货物(普通、加急、危险品)有不同的计费规则,我没有在Service层用if-else判断,而是将计费逻辑getPrices()方法,分别实现在NormalGoodsDomain, ExpediteGoodsDomain等各自的子类中。
  • 运用多态简化上层逻辑:得益于此,Service层只需面向AbstractGoodsDomain这一抽象进行编程。在计算订单总价时,它可以遍历一个AbstractGoodsDomain的集合,并统一调用item.getPrices()方法,而无需关心当前项的具体类型。JVM的动态绑定机制会确保正确的计费逻辑被执行。这不仅极大地简化了Service层的代码,更是对开闭原则的一次完美践行。
  • 提升封装性:在设计新的领域对象时,我开始有意识地控制getter方法的暴露。对象应该只暴露完成其职责所必需的接口,而非其内部的每一个状态。这是向更健壮封装迈出的重要一步。

通过这次从简化MVC到初步DDD的演进,我深刻体会到,DDD并非遥不可及的理论,它是一种能切实解决软件复杂度问题的务实方法论。它让我的代码充满了“业务智慧”,也让我对封装的理解,从“隐藏数据”的表层,深化到了“捆绑数据与行为,提供稳定服务”的内核。当然,这只是DDD实践的初步探索,对于聚合根、限界上下文等更深层次的设计问题,还需要在未来进一步思考和完善。

GUI架构模式的演进之路

本课程在JavaFX模块的翻转课堂模式,给予了我极大的自由度和探索空间。这成为了我学习过程中的一个重要“加分项”。我没有将自己局限于完成实验,而是以此为契机,在一个自设的、有趣的项目一个简单的Lua语言解释器中,对GUI应用的架构设计进行了一次完整的、由浅入深的自主探索。

起点与困境:经典的MVC与“胖控制器”问题

当我开始构建这个Lua解释器时,脑海中浮现的第一个模式便是经典的MVC。但我很快发现了问题:在JavaFX中,由于我使用的是声明式编程,并利用SceneBuilder,这个情况下,Controller与View(FXML)的耦合是天然的。
这导致Controller极易变得臃肿,难以测试和维护,虽然在这个小项目内无法很好的体现,但是能明显的感受到可扩展点的丧失,类职责的线性上升。

第一次进化:向MVP(Model-View-Presenter)的探索

由于我之前使用Qt时,大量使用了MVP模式,为了给Controller“瘦身”,我自然地想到了它的改进版——MVP。其核心思想是让Presenter成为中心协调者,View变得完全被动。这无疑是一大进步。
但View和Presenter之间需要定义和维护大量的接口,代码显得有些繁琐,但是在这个项目内,这已经完全足够了。
但是JavaFx作为可能比Qt更现代的框架,其天生支持数据绑定,这里能带来什么启发吗。

终极方案:在Lua解释器中完美实践MVVM

最终,我发现了为现代UI框架量身定做的MVVM范式,并在我的Lua解释器项目中,彻底地实践了它。

  • Model: LuaService,一个纯粹的后端服务,封装Lua与Java的交流。
  • View: 由FXML声明式地定义,Controller极简。
  • ViewModel: LuaInterpreterViewModel,整个架构的灵魂。它持有Model,并向View暴露可绑定的JavaFX属性

这种模式的优雅之处在于,通过数据绑定(Data Binding)取代了手动的UI更新,实现了View和ViewModel的彻底解耦,并使得ViewModel这个纯Java类可以被轻松地进行单元测试。这次从MVC到MVP再到MVVM的完整探索与实践,让我深刻地理解了GUI架构模式的演进脉络和其背后的设计哲学。

课程回顾与改进建议

在深入体验了课程提供的思考空间后,我也形成了一些不成熟的想法,希望能为课程的未来发展提供一些参考。

  • 引入与设计模式强相关的引导性实验 我个人在JavaFX中自主实践MVVM的探索带来了巨大收获。因此,我建议课程未来可以考虑正式引入一些与设计模式强相关的引导性实验,例如,在讲解GUI编程时,可以配套一个简单的项目并引导学生实践MVVM模式,这能让更多同学直观地感受到架构设计带来的好处,远比单纯的理论讲解来得深刻。
  • 组织正式的“代码评审”(Code Review)环节 一个人的思考总有局限。在每次大作业迭代后,可以考虑组织小组或班级范围内的Code Review活动。让同学们互相讲解自己的设计思路,展示代码,并接受同伴的提问和建议。看到他人优秀的设计以及背后的思考,其收获将远超独立工作,也能将非正式的个人问询,变成一个能让所有同学受益的、宝贵的学习环节。

总结章

这门课程为我搭建了一座至关重要的桥梁,连接了“掌握语言语法”与“运用语言构建高质量软件”这两个层面。它所开启的这扇关于“软件设计”的大门,让我意识到,学习的道路才刚刚开始。站在当前这个节点,我为自己的下一阶段规划了清晰的学习路线:

  • 深入DDD架构,探索复杂业务的终极之道 我在“航空订单管理”项目中对DDD的探索仅仅触及了皮毛。未来,我计划系统性地学习DDD的战略设计部分,深入理解限界上下文(Bounded Context)上下文映射图(Context Mapping)等核心概念,并探索CQRS事件溯源等高级模式,以期能驾驭真正复杂的业务系统。
  • 拥抱质量内建,跨语言学习单元测试与TDD 本次的所有项目都缺乏有效的自动化测试。我深刻认识到,测试是软件质量的基石。因此,我的学习计划将跨越语言边界:在Java生态中,我将系统学习JUnit等主流框架,掌握MockStub等测试技术;同时,考虑到我过往的C++经验,我也会深入学习C++的单元测试框架,例如强大的Boost.Test。最终目标是在个人项目中引入TDD(测试驱动开发)的开发模式,真正做到“质量是内建的,而非检测出来的”。
  • 知识迁移与升华,将MVVM应用于Qt开发 在学习JavaFX之前,我曾使用C++/Qt进行过GUI开发,当时采用的是MVP模式。如今回头看,虽然MVP解决了部分问题,但其接口的冗余和手动更新UI的方式仍有改进空间。我计划将此次在JavaFX中学到的MVVM思想,迁移到Qt开发中。利用Qt强大的信号与槽机制(Signals & Slots)来模拟数据绑定,重构之前的项目,实现一个更高内聚、更低耦合的Qt应用架构。
  • 学习依赖注入,应对组件耦合的有效方案 在我的Lua解释器项目中,ViewModel需要手动创建LuaService实例,但是Lua有很多种版本,如LuaJIT与原生Lua5.1,这种耦合关系在大型项目中是脆弱的,虽然可以通过添加层次的方式将创建和类解耦合,但这个时候,我也敏锐地意识到,这正是依赖注入(DI)控制反转(IoC)可以发挥作用的场景。因此,我计划深入学习Java生态中最经典的DI框架——Spring,借此掌握现代大型应用开发的基石。
posted @ 2025-06-19 10:28  NyanInt  阅读(31)  评论(0)    收藏  举报