23年春面向对象第四单元分析和总结

23年春面向对象第四单元分析和总结

目录

概述
UML
  UML 类图
    UML 类
    UML 关系
  UML 状态图
  UML 顺序图
架构设计
OO 课程中的架构设计与测试
  OO 课程中的架构设计
  OO 课程中的测试
OO 课程结束之后

概述

  OO第四单元的主要内容在 UML(Unified Modeling Language 统一建模语言) 图上。具体来讲有类图,状态图和顺序图。承载这一单元的题目背景是图书馆借阅系统。具体来说,各次作业要求如下:
  HW13: 实现一个图书借阅管理系统,书籍分三类,各自具有一定限制进行管理。需要实现预约功能。要求作一 UML 类图。
  HW14: 在上一次作业的基础之上,增加为复数个借阅管理系统,相互交互。需要实现跨馆流通。要求作一 UML 状态图。
  HW15: 基本无变动。要求作一 UML 顺序图。
  HW14 在增量开发过程中产生了难以控制的复杂度。流程控制逻辑混乱,指导书三番五次修改,作业三次延期,这恐怕在 OO 课了历史上都是罕见的。OO 自23年起,第三四单元已经远不再是前人口中的“养老单元”了,如果这篇博客有幸被后来人读到,切记不要大意相待。“勿谓言之不预也”。
  对于 UML 类图,在第一单元的博客中已经使用过。本单元重点在于 UML 各种图的理解和绘制,对于图的理解也要更上一层楼。现在回首第一单元做的类图,其中有不少谬误,笔者不一定有精力再去做修改,读者知道错误何在就好。

UML

  在 OO 课上,我们已经学习过了 JML 这种形式化的建模语言。与 JML 主要指导代码规格不一样的是,UML 图倾向于阐明程序在某一方面所具有的结构。这种结构从代码具体逻辑中抽象出来,具有更高层次的抽象。UML 在工业界的应用比较广泛,主要用于沟通和交流。一份好的 UML 图能够使得读者快速地对项目整体结构具有一个概括性的了解。
  UML 图分为很多种,每种都注重于阐述项目的某一方面。UML 类图主要描述了类所具有的变量、方法及其修饰,以及类与类之间的关系。UML 状态图类似于流程控制图,主要描述了一个特定对象在何种状态下会经过何种路径产生状态变化。UML 流程图则着重于时序控制,主要描述了类之间的具体交互和时序逻辑。不同 UML 图的一致性没办法形式化的判定,但是一份好的 UML 图应当尽量做到不同图的一致,避免产生自相矛盾。

UML 类图

  UML 类图主要可以分为两个部分。一个部分是各种以方框形式存在的组合体,最常使用的是类。另一部分是组合体之间的各种关系,主要有泛化、实现、依赖、关联、聚合、组合五种类型。

UML 类

  在 UML 类图中,类以一个方框的形式存在,其可以包含属性和方法。如果是接口,则应该在类名上注明<<interface>>
  属性应该遵循可见性 属性名: 类型的方式定义。而方法应该遵循可见性 方法名([参数名]: [参数类型], ...): 返回值类型的方式定义。java 构造方法则无需注明返回值。

UML 关系

  五种关系中泛化和实现主要描述的是类之间的继承和实现关系,而依赖、关联、聚合、组合则主要描述的是类之间的相互调用关系。

  • 泛化,也称继承,对应 java 中的extends,由子类指向父类。
  • 实现,即implement,由实现类指向接口。
  • 依赖,体现在一个类通过成员变量、局部变量、方法参数、方法返回值调用了另一个类。依赖是一种暂时的联系,不会长期存在。由调用类指向被调用类。
  • 关联,同样体现在一个类通过成员变量、局部变量、方法参数、方法返回值调用了另一个类。关联是一种长期性的联系,比依赖更强。由调用类指向被调用类。
  • 聚合,反映的是整体与部分的关系,主要通过实例变量来实现。聚合关系中的被调用类仍然存在一定的自主性,离开了被调用类,其能够单独存在。比如说鸟巢和鸟就是一种聚合关系。由调用类指向被调用类。
  • 组合,同样反应的整体与部分的关系。组合中的被调用类在离开了调用类之后就没有存在的意义,是一种强化的聚合关系。比如鸟和翅膀就是一种组合关系。由调用类指向被调用类。

UML 状态图

  状态图同样主要包含两类元素,一类是状态,一类是状态之间的转移。
  一个完整状态通常包含两个部分:必选的状态名和可选的内部转移域。内部转移是相对于外部转移(也就是有转移箭头)的转移而言的。

属性 定义 说明
状态名
进入动作 entry / 方法名 转入该状态时,做一次进入动作
退出动作 exit / 方法名 退出该状态时,做一次退出动作
内部动作 do / 方法名 在该状态内部进行的转移
事件 event 原因 / 方法名 满足诱因事件后,执行该方法

  状态的转移从原状态指向目标状态。一个完整的转移具有三个部分:触发事件,监护条件和动作。按照触发事件[监护条件] / 动作来定义。某一个转化发生的充要条件是,该转换的触发事件发生,并且满足监护条件,则进行一个可选动作并迁移到目标状态。

UML 顺序图

  顺序图包含的内容很多,有参与交互的对象,有对象的生命线,有对象之间的消息,有激活条,有组合片段。
  参与交互的类按照实例名: 类名来定义。如果不关心对象的具体名字,则可以只写: 类名来表示一个匿名对象。
  对象的生命线代表了该对象的存在周期,在该周期内,该对象可以与其他对象发生交互。有两种特殊的消息:createdelete用来创建一个新对象和结束它的生命线。
  对象之间的消息有三种:异步消息,同步消息,返回消息。

消息类型 说明
同步消息 同步消息表示调用者在调用了被调用者的方法后停止活动,等待被调用者返回。同步消息为实心箭头,由调用者指向被调用者
异步消息 异步消息表示调用者在调用了被调用者的方法后不等待返回值,继续活动,常用于并发场景。异步消息为单箭头,由调用者指向被调用者
返回消息 返回消息总是与同步消息成对出现,表示调用者向被调用者返回返回值。同步消息为虚线单箭头,由被调用者指向调用者

  激活条表示一个对象执行一个动作经历的时间段。该对象在激活条时间内被占用。
  组合片段可以用来表达更加复杂的逻辑序列。组合片段的种类包括:opt, alt, par, loop, break等。

种类 名称 说明
opt 可选 满足条件则发生,不满足则不发生。是对if的抽象
alt 选择 满足某一条件则触发某一框内序列,如果指定了else,则在所有条件都不满足时执行else内的序列。是对if ... else if ... else的抽象
loop 循环 循环一定次数。是对for的抽象
par 并行 并行处理,并行序列中的事件可以交错。是对多线程的抽象

架构设计

  本次作业的架构如下。

  本次作业的指导书当中,明确的定义了多种管理员的服务职责和权限范围。尽管我也遵照了指导书的思路,但是却是不必要一定这样架构的。为了统一管理和共享容器,所有的容器均由Library类持有,各个不同的管理员不过实现几个方法进行数据处理。这谈不上是好的面向对象的架构,类之间的耦合度因为容器的共享变得很高。如果去掉所有不必要的管理员类,将所有的方法均放入Library类当中,又有类行数超标的可能性。如果将容器改为由单独的类对象统一持有,可能会更加合理一些。
  状态图和顺序图因为画的很烂,就不放出来献丑了。

OO 课程中的架构设计与测试

OO 课程中的架构设计

  首先,笔者十分清楚的一点是:一个好的,结构清晰,分拆合理,可扩展性强,符合面向对象设计哲学的架构能够使得在接下来的一个月中都受益匪浅。但是,架构的优化是无止境地,针对每一个人的习惯,不同的架构有有不同的实用性。我们并不能指望第一次尝试就找到最合适的架构,这是一个不断试错,不断优化的过程。
  为了更好地达成一个好的架构,在经历过 OO 课程的训练后,笔者有了以下几点认识:

  1. 首先应当通读实验指导书,理清题面所提供的解题思路。这种解题思路通常来说是由数量众多的限制堆砌而成的,类似于可行域,而我们的架构应该是可行域中的一个解。读懂题面有助于我们整理清楚控制的基本框架,数据的基本流向,对整个任务有一个总体上的认识。
  2. 其次应当找出题面当中施加了特别限制的地方。它们通常规定了数量上的约束,时序上的先后关系,逻辑上的容斥关系。这些施加了特别限制的地方通常是测试中的重点考察对象。有一些限制是为了抽象问题,使得问题简化,而另一些约束则是为了增强问题,提高课程的区分度。这些限制通常比较反直觉,所以通常也容易被遗忘。当你在实现过程中回忆起来并且想办法实现它时可能已经积重难返了。
  3. 然后应该运用面向对象的思想对问题进行拆分。面向对象的三大要素:封装,继承,多态。在这个时候就应该发挥作用。哪些元素在逻辑上是一个整体,应该抽象为一个类?哪些元素与哪些元素具有逻辑上的关联关系,应该使用继承或实现的方式明确?将问题分隔为一个个相互协作的类之后,架构的整体框架就搭建出来了。值得注意的是,这里的抽象是一种低层次的抽象,也就是有具体事物抽象到具体类,类和事物之间存在明确的对应关系。我们稍后还会改进它。
  4. 到了这一步后,我们就可以开始着手写代码了。不必也不应该急于求成地将整个框架都搭建起来。经过了上一步的分隔,我们应该拥有了很多可以独立工作的模块。我们应该直接着手某一模块(具体来讲就是某一个类)的搭建。最后的项目应该是像用乐高拼积木一样水到渠成。当然,在搭建模块的过程中,我们肯定会遇到在上一步中没有料想到的情况。这个时候,我们就应该诉诸更高级的抽象层次,对模块进行更加抽象地划分。这一步的抽象通常是工具性的,我们通常会诉诸既有的设计模式、算法、解决思路对问题进行进一步的抽象,抽象出来的模块与现实事物间不一定有明确的关系。比如在电梯单元中抽象一个侯乘列表,在第四单元中抽象许多图书馆工作人员。它们的存在是为了使得编码变得更简单。如果违反了这一原则,那么这种抽象就没有存在的必要。
  5. 最后,在完成了所有的编码工作以后,我们应该回顾整个项目。也许其中有实现得并不那么优美的地方,也许你可以容忍这种不优美然后通过大量的额外补正让其正常运作。也许你会走上重构的终极道路。是的,重构从来都不是可以避免的选项。很多时候,与其思考如何使得自己的代码一劳永逸,永不重构,倒不如花点心思封装一下模块,以期在重构的时候可以复用。这个时候,你应该回到第一条重新开始。除了所剩无几的时间,你应该还有对于整个项目更深一层的理解。

  架构是一个项目在完成之后所呈现的形态,在没有完成之前无法准确预测,在完成之后难以轻易修改。正因为如此,架构才十分重要。正因为如此,架构能力的高低才是区分一个程序员水平高低的终极标准罢。

OO 课程中的测试

  我必须在此对所有在 OO 课程当中,在讨论区,课程网站或社交媒体上分享了自己测评机、测试数据的大佬们致以最崇高的敬意。很难想象笔者如果不借助神奇的外部力量,OO 成绩会怎样的惨淡。OO 这门课程不同于以往课程的课程设置使得测试成为了影响得分的最重要方面。架构好坏无非是一时半会的差别,OO 课程成绩的高低则是永恒的主题。
  很遗憾的是,笔者在整个 OO 课程当中,始终没有能够拿出自己的自动化测试工具。笔者在第三单元饱尝了测试不完全的苦果。在第四单元,笔者希望写自己的测评工具。可这测评工具岂又是易事?即使完全诉诸随机生成数据,也需要剔除当中不满足题目要求的部分。如果能够对题目要求有完全的认识,那么本体程序也不应该出现bug。
  这成为了一种悖论。因此笔者格外敬佩那些做测评机并无私分享的大佬们。
  笔者在本单元的自主测试基本为手捏数据。手捏数据通常是对单一限制的考察。没有大规模,自动化的方式进行覆盖测试,没有办法仅通过手动测试保证程序的正确性。

OO 课程结束之后

  OO 课程总算是结束了,实在是如释重负。这个学期尽管还有 OS, 离散这样较之 OO 一样重要的大课,OO 对笔者产生的压力是最大的,笔者在其中投入的时间也是最多的。在 OO 结束之后,像笔者这种颁奖典礼观众,很难说对 OO 有什么深厚感情。尽管笔者的 CO 成绩一般,助教也落选,但是 CO 从 P4 到 P7 最终实现一个流水线寄存器的成就感是不可同日而语的。OO 课程倒是无愧于“昆仑课程”的定位,全程给与笔者的只有压力与忐忑。
  写完这篇博客,也就给 OO 画上了尾声。虽然23年的 OO 课程出现了一些问题,但是总的来说,OO 课程还是给我带来了很多收获。这些收获首先体现在代码能力的增强。现在,千余行代码对于笔者来说已经没有什么触动,花一整天从早到晚坐在电脑前对于笔者来说也成了稀松平常的事。笔者相信,只要见识过 OO 的零鸭蛋,便没有什么困难是不能克服的(笑。其次收获在对面向对象的学习和认识上。面向对象的基本原则和基本方法已经深入地影响到了现代软件工业的方方面面,符合面向对象原理的代码,更容易被理解和复用,减少工作量。这对今后迈向工业界打下了一个很坚实的基础。最后收获体现在锻炼了我的承压能力上。大课重课接连不断,这是6系的常态,OO 不过是其中的一环罢了。笔者已经从上个学期每临 CO 上机便夜不能寐到现在被锻炼成挂掉强测仍能安然入睡的程度。说是成长也好,说是习惯也罢,OO 确实使得笔者的抗压能力显著提高。
  如此便好,课程之外,诸君安康。

posted @ 2023-06-19 21:26  topady  阅读(47)  评论(0)    收藏  举报