一、第四单元作业架构分析
面向对象-UML层次化设计
更多参考:https://www.cnblogs.com/jiangds/p/6596595.html
https://www.cnblogs.com/jiyuqi/p/4571543.htmlUML顺序图https://www.cnblogs.com/yxx123/p/5227272.html
UML状态图https://blog.csdn.net/w36680130/article/details/81014032
通过StarUML和notepad++工具查看并总结
UML将类(class)、接口(interface)、方法操作(operation)、属性(attribute)等都视为对象元素(UMLelement),UMLModel容器管理着模型的所有元素
-
- UMLClass 对应用户所构造的类
- UMLAttribute 对应用户构造的类的属性
- UMLAssociation 对应用户构造类之间的关联性
- UMLAssociationEnd 对应用户构造类互相关联性
- UMLGeneralization 对应用户构造类的泛化/继承
- UMLInterface 对应用户构造的接口
- UMLInterfaceRealization 对应用户构造接口的实现
- UMLOperation 对应用户构造类的方法操作
- UMLParameter 对应用户构造类的方法操作的参数
何为模型?
UMLModel描述各种<UML***>类型元素及其关系(一种图数据结构)……图片来自同学大佬
UML与mdj代码解析
-
- 一个项目有一个Model容器,容器下直接有多个类和接口
- 每个类和接口下有孩子元素,其中Operation元素下还有Parameter元素,Association元素下有AssociationEnd元素
- 每个元素通常有成员_parentId, _id, _type, name, type, visibility, direction, source(源类), target(目标类), reference(相关的两个类,正常情况分两个end元素), navigable(关联方向)等property
- 类和接口(在mdj代码中属于同一级别)的parentId总是一致的,并且类和接口的parentId为Model的id(parentId不代表任何泛化,关联等意义,仅代表层次结构关系,所有类和接口的parentId都是一个)
- 那么如何确定顶级父类?
利用id进行递归查找,或者并查集思想,主要是考虑递归边界:顶级父类的Generalization元素为空 - 一定情况下,生成的mdj代码是具有一定顺序的,类的操作元素和属性元素都承接在类元素后
- 实际的Operation出现在它自己的Parameter中,为了区分参数和操作,可通过Direction标志是否为return来区分,(void)类型的操作的Direction标志也是return,与具有返回类型的操作区分为type标志
类之间的关系
-
- 泛化/继承(Generalization),Java单继承,UML可以多继承
- 接口(Interface)
- 接口实现(InterfaceRealization)
- 关联(Association)是一种拥有的关系,它使一个类知道另一个类的属性和方法
- 组合(Composition)是整体与部分的关系,但部分不能离开整体而单独存在
- 聚合(Aggregation)是整体与部分的关系,且部分可以离开整体而单独存在{none, shared, composite}
- 依赖(Dependency)表示一个类A依赖于另外一个类B的定义
UML:接口可以继承接口,也可以实现接口;
Java:接口可以继承接口,但不可以实现接口。
类属性和操作的数量
-
- 注意区分操作是类的还是接口的
- 类B通过继承而自动获得父类A的所有属性和操作(只是子类不能直接访问父类私有属性),因此
B.attriCount = A.attriCount + B.self.attriCount (rule 1)
B.operaCount = A.operaCount + B.self.operaCount (rule 2)
- 接口实现不会建立任何上层和下层在属性、操作和关联关系数量上的连接。类有多少属性/操作/关联时,可以直接无视接口实现关系。
关联关系
对于关联下的元素
-
- 一种是互相关联Association
关联正常情况下会有两个end元素,都是AssociationEnd类别
- multiplicity对应的是该关联端对应的个数
- reference代表UMLAssociationEnd所对应的类的id
- 另一种是直接关联Directed Association
navigable(关联方向),reference同上,navigable为false(未打勾)的时候代表指向类,为true(打勾)时代表可被指向类
- 聚合aggregation
- 两个类关联时,关联信息出现在其中一个类中,另外一个类需要通过Reference判断是否被关联
- 关联有两个端点,每个端点可以有一个基数,表示这个关联的类可以有几个实例。
0..1 表示可以有0个或者1个实例
* 表示对实例的数目没有限制
1 表示只能有一个实例
1..* 表示至少有一个实例 - 在代码层面,被关联的类B以类属性的形式出现在类A中,也可能是关联类A引用了被关联类B的全局变量。
- 在Java中,关联关系是使用实例变量来实现的,类定义
- UML和Java一致性:类能够和其他类或者接口相关联,接口也可以
- 一种是互相关联Association
关联性是否会被继承下来
答案是肯定的,子类拥有父类的所有关联关系,类可以自关联,自关联相当于两次关联
F.assoCount = D.assoCount + F.self.assoCount (rule 3)
依赖
-
- 依赖关系时是单向的,也是弱关系,在代码层面,A类中使用到了B类,类B作为参数被类A在某个方法中使用
- 在java中,依赖表现为:局部变量,方法中的参数和对静态方法的调用。
聚合关系是关联关系的一种,是强的关联关系
组合关系是关联关系的一种,是比聚合关系还要强的关系
UML与Java具有一致性
-
- 一个非抽象类必须实现接口中定义但未实现的所有操作
- 一个类可以实现多个接口
- 实现类需要显式列出要实现的操作,而继承则不需要显示列出父类的操作
将每个类和接口作为根节点建立树结构
实质上,UML层次已经显示出来了,通过ref(隐式指针)和_parentId来连接各个树结构形成森林
顺序图Sequence Diagram -> UMLCollaboration
collaboration和Model同层次,Role与interaction(交互行为)为该顺序图的上层,拥有以下的交互对象,sequence,participant 参与交互的对象
功能:获取参与对象数,获取消息数,获取对象的进入消息数
-
- UMLLifeline 生命线
- UMLMessage 消息传递
- UMLEndPoint 顺序图终结点
计算总的参与对象
每条生命线对应一个对象
计算总的交互消息
UMLMessage的数量
计算制定对象的incoming消息
接收到的消息数量
顺序图与mdj代码解析,并建立关系图
-
- UMLInteraction的下一层有UMLLifeline,UMLMessage
状态图StateChat Diagram -> UMLStateMachine
StateMachine和Model同层次
状态图表示一个类的行为,功能:获取状态数,获取状态机转移数,获取后继状态数
-
- UMLRegion 状态图范围,包括状态和转移信息
- UMLState 在UMLRegion下一个层次
- UMLPseudostate 初始状态,获取kind可得到initial标志
- UMLFinalState 结束状态
- UMLTransition 和UMLState同一个层次
- UMLEvent 转移的响应事件
- UMLOpeaqueBehaviour 状态转移的结果
状态的计数(包括初始和终态且只算作一个)
UMLState元素,可直接获得状态名称
一个状态的不同的后继状态(可达的状态,同样包括初始和终态,且只计算一次)
又是一个递归建图的问题,通过该状态UMLTransition元素查找
只要可达的状态都算后继状态,注意处理同名同类型的状态(有可能同名但不同类型)
状态转移计算
UMLTransition元素,source&target
- triggers中的UMLEvent 代表转移触发条件,可从UMLEvent元素中获得转移标志名称
状态图与mdj代码解析,并建立关系图
-
- 顶层UMLRegion,其parent_id为UMLStateMachine
- 下一层有UMLPseudostate,UMLState,UMLFinalState,UMLTransition
- UMLTransition的下一层有UMLEvent
模型有效性与一致性问题
具有UML检查规则,即应当满足状态图自身、顺序图自身、类图自身、三个图之间的对应规则
UML基本标准预检查功能
- UML002:不能含有重名的成员。针对类图中的类(UMLClass),其成员属性(UMLAttribute)和关联对端所连接的UMLAssociationEnd不能有重名
- UML008:不能有循环继承。该规则只考虑类的继承关系、类和接口之间实现关系,以及接口之间的继承关系。
- UML009:任何一个类或接口不能重复继承另外一个接口。该规则考虑类之间的继承关系、接口之间的继承关系,以及类对接口的实现关系,包括直接继承或间接继承。
第一次作业——UML类图解析
任务
实现一个UML类图解析器UmlInteraction,可以通过输入各种指令来进行类图有关信息的查询。
查询指令:模型中一共有多少个类、获取类中指定类型的操作(本类自己定义的)数量、类中的属性有多少个、类有几个关联、类的关联的对端是哪些类、类的操作可见性、类的属性可见性、类的顶级父类、类实现的全部接口、类是否违背信息隐藏原则。
设计思路
UML交互接口的实现类MyUmlInteraction
* 通过className建立树结构,每个class对自己类进行相应的计算
* 通过className映射类元素
* 通过元素自身id映射元素(id不会重复的原则)
架构设计
基础层
- MyUmlInteraction 交互接口的实现类
数据结构层
- ClassTree 类结构树。通过className建立树结构,并对树结构内进行相应计算
* 一个项目有一个Model容器,容器内有多个类、接口等
* 每个class下有孩子元素,其中Operation元素下还有Parameter元素
* 每个元素都有成员_parentId, _id, _type, name, type, visibility, direction, source(源),
* target(目标), reference(相关的两个类,正常情况分两个end元素), navigable(关联 方向)等property
- InterfaceTree 接口树,功能同ClassTree类
- AssTree 关联树,仅保存关联对端,而忽略类本身
- OperaTree 操作树
缓存计算层
- MyUmlInteraction类通过关联其他数据结构类,当数据结构建立后获得数据结构得出计算结果并保存,方便下次查询。
应用层
- UmlInteraction类图指令查询功能的接口
Debug
本次作业的bug:并不保证输入元素具有顺序性,id是唯一区分元素的标志,类和接口会同名(java中不同包内允许),父类和子类的属性可同名不同可见性。
容易出错:类实现的所有接口,包括接口的多继承接口,包括同名不同id的接口也要列出来;继承父类的关联(父类也可能有继承)。
大多数问题是对类图层次分析不够明确和彻底,对mdj代码和UML图型的对应关系没有理解透彻,造成很多考虑没到位,或者理解错误的地方。
度量分析
类图
本身实现接口的类不多,只有一个实现接口的类,满足用户查询需求。但需要建立数据结构的类非常多,也是本次作业的重点,查询功能的代码不难写,难在建立数据结构和UML层次关系的解析。只要建立好了数据结构,计算结果很容易得出。
代码分析
复杂度分析
通过树和图建立数据结构,通过递归或者dfs搜索计算,代码深度和类图层次关系较为复杂。
第二次作业——UML类图、顺序图、状态图解析及基本规则验证
任务
在第一次作业基础上,实现一个UML类图、状态图、顺序图解析器UmlGeneralInteraction,可以通过输入各种指令来进行类图有关信息的查询,并能够支持几个基本规则的验证。
查询指令:除第一次外还有:给定状态机模型中一共有多少个状态、给定状态机模型中一共有多少个迁移、给定状态机模型和其中的一个状态,有多少个不同的后继状态、给定UML顺序图,一共有多少个参与对象、给定UML顺序图,一共有多少个交互消息、给定UML顺序图和参与对象,有多少个incoming消息、模型有效性检查(针对下面给定的模型元素容器,不能含有重名的成员(UML002)、不能有循环继承(UML008)、任何一个类或接口不能重复继承另外一个接口(UML009))。
设计思路
UML交互接口的实现类MyGeneralInteraction
* 通过className等信息建立树结构,每个class对自己类进行相应的计算
* 通过className映射类元素
* 通过元素自身id映射元素(id不会重复的原则)
实质上,UML层次已经显示出来了,通过ref(隐式指针)和_parentId来连接各个树结构形成森林。
架构设计
基础层
- MyGeneralInteraction 交互接口的实现类
- UmlRule002 规则检查类
- UmlRule008 同上
- UmlRule009 同上
数据结构层
- ClassTree 类结构树。通过className建立树结构,并对树结构内进行相应计算
* 一个项目有一个Model容器,容器内有多个类、接口等
* 每个class下有孩子元素,其中Operation元素下还有Parameter元素
* 每个元素都有成员_parentId, _id, _type, name, type, visibility, direction, source(源),
* target(目标), reference(相关的两个类,正常情况分两个end元素), navigable(关联 方向)等property
- InterfaceTree 接口树,功能同ClassTree类
- AssTree 关联树,仅保存关联对端,而忽略类本身
- OperaTree 操作树
- MachineTree 状态机结构
- Region 状态机层次结构
- State 状态树
- Interaction 顺序图交互结构
- Lifeline 生命线(交互对象)树
缓存计算层
- MyGeneralInteraction类通过关联其他数据结构类,当数据结构建立后获得数据结构得出计算结果并保存,方便下次查询。
应用层
- UmlClassModelInteraction类图指令查询功能的接口
- UmlStandardPreCheck 规则检查(预先)接口
- UmlCollaborationInteraction 顺序图交互查询功能的接口
- UmlStateChartInteraction状态图指令查询功能的接口
Debug
状态图:
1.计算状态数量的时候初始和结尾状态只算一个
2.一个StateMachine下保证只出现一个Region,且final和pre状态都只作为一个,且一个Machine中状态不重名
3.如果初始或者结尾状态是后继状态算
4.每个State Machine中,从某个状态到另一个状态的直接迁移均具有不同的Event或Guard,即从某个状态到另一个状态的直接迁移若有多个,则这些迁移一定互不相同。
5.transition的parentid为region,因此用source。
6.容易爆空指针以及递归栈溢出,没有处理好递归边界(循环继承的例子)。
顺序图:
-
- 对象和lifeline是否对应,多个lifeline可以引用一个对象,但保证Lifeline和其Represent均一一对应。
规则验证:
-
- 重复实现也算,通过实现继承也算。
- associationEnd可能name为空
大多数问题是对类图、状态图、顺序图层次以及规则分析不够明确和彻底,对mdj代码和UML图型的对应关系没有理解透彻,造成很多考虑没到位,或者理解错误的地方。
度量分析
类图
本身实现接口的类不多,有实现4个接口的类,在第一次作业上增加状态图、顺序图交互、规则检查接口,满足用户查询需求。但需要建立数据结构的类仍然非常多,总共达到13个类,也是本次作业的重点,查询功能的代码不难写,难在建立数据结构和UML层次关系的解析。只要建立好了数据结构,计算结果很容易得出。
代码分析
复杂度分析
模型图增加了状态图和顺序图,范围广了,但建立数据结构的方法类似第一次。
二、OO作业四个单元的架构设计及其面向对象构造的演进
第一单元:
面向对象设计的初步,刚开始还不太会运用面向对象思想编程,对于求导问题还是很直接地写面向过程的代码。架构设计几乎也没有,很乱,没有交互层面。一次作业仅仅3-4个类:表达式、主函数、求导、数据结构类。
第二单元:
多线程程序不仅有数据结构类、输入输出类,还增加了与需求交互的类,还有多个线程并发的情况——线程类,还有管理(调度)类。第二次作业逐步体现出抽象的过程,将需求和功能分别对应。
第三单元:
JML设计,在此次作业中主要是实现功能接口,也学习到了部分软件工程设计模式(工厂化模式)。这个单元对类的抽象和封装有更高的层次,我们根据官方接口,实现自己的容器,用于用户查询等操作。在架构设计上,还增加了缓存计算层,将数据层与应用层独立开来。
第四单元:
同第三单元类似,只是在数据结构层上增加了深度和难度,结构观察视角和行为观察视角。设计模式和第三单元差不多,都有缓存计算层,实现接口功能的应用层则更好地体现了用户需求与数据的交互,但不会影响数据结构层。
作业采用了迭代增量的方式每个单元的三次代码作业,都是基于原本功能进行扩展,增加更多需求和操作。
三、OO作业四个单元的测试方法与实践(bug修复)的演进
第一单元:
测试没有太多技巧,主要是通过肉眼观察表达式匹配的字符串,以及碰撞边界特殊表达式的情况,同时,手写评测器疯狂对代码进行测试,虽然效率增加了,但很难保证测试范围和测试质量。
第二单元:
本单元加入多线程,调试难度加大,甚至无法用断点调试以及找出死锁问题。作业的调度算法增加了难度,需要考虑时间优化,所以逻辑上、优化上出现了很多问题。线程调度、线程安全是测试的重点,但测试方法没有找到很合适的。对于输出结果可以利用对拍器自动比较。
第三单元:
通过学习JML语言后,运用jmlunitNG等工具编写测试类进行代码测试。这个单元的bug主要是第三次作业,需求增加到四个计算,显然很多Bug,中测倒数第二个最后关头都没找出来,所以没进互测,然后时间复杂度特别高,对于强侧的数据,简直爆炸了。
修复过程中,改进了求最短路的算法,修复了超时的问题。对于最低票价和不满意度是最容易存在Bug的,而对于并查集求解基本没有问题。最低票价我采用上文的思路,仍会出现问题,有这几种:单条路径存在环路需要更新权值;每条路径都需要求最短路;新增路径与原有路径存在相同节点也需要更新。
第四单元:
学习了UML模型图以及运用starUml工具进行模型图构建、生成mdj代码,方便观察和测试,同时可以使用前几次作业的测试方法和测试类。
四、OO课程收获与体会
学期刚开始,甚至还没开学(还在家里)的时候我们就接收到本学期面向对象课程预习作业的通知,我也是本学期才接触到Java编程及其面向对象编程思想,总体上的感觉就是,这门课程让我从一无所知变成学会了很多编程思想、测试、调试、架构设计、多线程、软件工程设计模式等方法(不过我依然是一个菜鸡)。那么具体来说一下这门课程中我的收获,如下:
1、熟练掌握Java语言及其IDEA编译器的使用。虽然一个学期的时间很短,但是课程任务不是一般的重,而是超乎常人的重。每周都有类似于大作业、大工程的代码项目,那就是必须在每周前三天较好、较快地完成代码编写、调试和通过基础测试,在通过基础测试后,接着两天内进行同学之间的匿名互测(互相hack,我学会如何做一名hacker哈哈),最后官方评测系统再根据较强测试进行评分,最后还有几天的bug修复环节。除此之外,每两周有一节上机实验课和一节研讨课,每一单元作业完成后对单元作业进行总结的博客编写(例如现在写的就是)。对于我们来说,每周都是紧张的状态,每周的代码量也都充足,所以短短地一学期,能够熟练运用IDEA和Java面向对象设计是一大收获。
2、个人的自学能力和思维能力得到提高。我个人(或者说大多数人)在这门课程的作业上花费的时间应该是最多的了,这门课程也是最烧脑、最磨时间的课程了。每个人对程序、工程都有自己的独立思考和转化,能完成这些代码量的程序说难不难,说易也不易,也挺佩服大家的毅力。
3、课程带来了面向对象编程思想和软件设计模式这些新编程方法。这些思想方法更贴近软件的设计和用户的需求,掌握多种设计方法,能够让我们(程序员)更好地应对客户需求。一开始从面向过程编程到面向对象编程难免会有不适应,无法理解类的封装与独立,熟练之后,觉得每个功能、每个类别的东西独立开来,还可以互相交互更方便设计。之间加入多线程则更是符合实际生活需求,生活中多线程的例子很多,滴滴打车,银行排队等,这些也在实验课中完成过,多电梯多调度作业也编写过。
4、掌握多种程序测试方法及其自己编写评测器还有bug修复方法。写程序当然少不了测试,谁的程序都避免不了出现bug,bug修复也是一个难题,甚至连bug在哪都有可能找不到。课程增加了同学之间的hack,不仅需要找自己代码的bug也要构造测试用例找别人的bug,在此过程中也学会了编写基本的对拍器,自动评测器,加快hack效率和质量。在学会jml之后,还运用jmlunit等测试类工具进行测试代码。bug修复不比写代码花的时间少,其实写完代码后发现连基础数据都过不了很正常,现在的程序代码复杂度高,肉眼无法再观察得到了,只能通过调试、结果输出、猜想判定等方法找出bug。
5、如何在短时间内写出上千行代码(哈哈哈,这可能是程序员最骄傲的收获了,一周内写了上千行代码,是又想吐槽又骄傲又秃头~警告)。
五、OO课程评价与建议
我对本课程满意度还是挺高的,虽然确实折磨人,不过后来稍微降低了一点难度,心里畅快多了,之前的多线程单元确实头疼,基础测试都没过。高强度、高难度才能训练出好的工程师,这句话实测无错。
提点建议:
- 以后的互测,大家别先忙着写评测机如何如何“坑”别人,这样真的好吗?有的时候所有人平均hack次数2-5次,你一个人上百次(甚至是同一个bug),好玩嘛?纯属找娱乐感和自豪感。其实程序写完美类,完全不担心互测的问题,毕竟互测真正的目的是通过自己思维而不是写评测机自动hack(虽然这个没错)。
- 实验课有的时候感觉不知道要干嘛,或者说实验课做了太多好像没有意义的事。其实实验课没有必要安排在理论课的下一节,毕竟才上完理论课,还没弄明白,接着实验课就做实验,会比较迷茫。
- 理论课可能需要更具体更实际一点的讲解,或许有点过大、过宽、过深了。