面向对象第四单元总结&&学期总结
面向对象第四单元总结&&学期总结
一、本单元作业架构设计
在本次作业中,设计上分为两个阶段,第一个阶段是第十三、十四次作业,其在于对UML的建模与查询方法的实践,重点在于建模,第二阶段是第十五次作业,其要求在所建立的模型的基础上,对模型的有效性进行检查,重点在基于之前的设计对功能进行拓展。
1.1 UML成员及关系
1.1.1 类图结构
UML类图具有以下的基本元素,按元素类和关系类列出:
- 元素类
- UmlClass
- UmlInterface
- UmlOperation
- UmlAttribute
- UmlParameter
- UmlAssociationEnd
- 关系类
- UmlGeneration
- UmlInterfaceRealization
- UmlAssociation
其中顶层元素有UmlClass
和UmlInterface
两种
次级元素有UmlOperation
和UmlAttribute
次次级元素有UmlParameter
,其只从属于UmlOperation
UmlAssociationEnd
是一个比较特殊的存在,其用于作为UmlAssociation
的端点,可以引用UmlClass
和UmlInterface
而对于关系类的元素来说,UmlGeneration
的两端只能为两个顶层元素中的同类,UmlInterfaceRealization
只能由UmlClass
向UmlInterface
进行继承。
在继承、实现时,除了直接继承和实现,我们还需要考虑间接的继承和实现。
1.1.2 顺序图结构
UML顺序图中也具有多种元素,按照画布类、实体类、交互类三类列出如下:
- 画布类
- UmlCollaboration
- UmlInteraction
- 实体类
- UmlLifeline
- UmlAttribute
- UmlEndPoint
- UmlLifeline
- 交互类
- UmlMessage
顺序图的关系较为简单,其中只有两层画布,顶层为UmlCollaboration
,下含UmlInteraction
其中则只有UmlLifeline
和UmlEndPoint
两类相互发送消息的实体与复杂多样的UmlMessage
1.1.3 状态图结构
UML状态图中的元素可以按照画布类、状态类、转移类列出如下
- 画布类
- UmlStateMachine
- UmlRegion
- 状态类
- UmlPseudostate
- UmlState
- UmlFinalState
- 转移类
- UmlTransition
- UmlEvent
- UmlOpaqueBehavior
状态图的结构与顺序图类似,但是在转移是会有副作用,也就是UmlEvent
和UmlOpaqueBehavior
1.2 需求分析
本次作业中,三种图片之间几乎不存在耦合,是三个不同的模型,可以分别分析
1.2.1 类图需求分析
类图需要进行的查询是8条指令
- 模型中一共有多少个类
- 类的(直接继承)子类数量
- 类的(仅自己定义的)操作数量
- 类的(仅自己定义的)操作可见性统计
- 类的(仅自己定义的)操作耦合度统计
- 类的(含继承类而来的)属性耦合度统计
- 类的(之间或间接)继承的所有接口统计
- 类的继承深度
1.2.2 顺序图需求分析
顺序图的查询有3个指令
- 查询某个
Interaction
中的Lifeline
个数 - 查询某个
Interaction
中的Lifeline
对象的创建对象 - 查询某个
Interaction
中的Lifeline
对象收到了多少个Found
消息,发送了多少个Lost
消息Found
为来自EndPoint
的消息Lost
为发送至EndPoint
的消息
1.2.3 状态图需求分析
状态图的查询有3个指令
- 查询某一状态机中一共有多少个状态(包含
InitialState
和FinalState
) - 查询某一状态机中的某一状态是否为关键状态
- 查询某一状态机中引起两个状态之间(直接)迁移的所有触发事件
1.3 架构设计
对于每一类的图,我们都配备一个工厂类,由工厂来生产该类图的结构
1.3.1 类图架构设计
类图中需要重要体现的结构是包含、继承、实现、关联四种关系,而类图中有以下几种情况包含这一关系
Class -包含-> Attribute
Class -包含-> Operation
Interface -包含-> Attribute
Operation -包含-> Parameter
Class -单继承-> Class
Interface -多继承-> Interface
Class -多实现-> Interface
Class -关联-> Class
Class -关联-> Interface
Interface -关联-> Interface
Interface -关联-> Class
所以对于类、接口、方法三种类需要重写,且关联两端的元素可能是类也可能是接口,所以其需要拥有一个共同的父类或接口。
1.3.1.0 共同父类设计
类和接口具有一样的共同的属性:
- ID号
- 名字
- 属性
- 可关联性
所以需要围绕上面四个属性来设计配套的方法
1.3.1.1 类设计
类在除了共同的父类属性之外,还有方法、子类、父类、实现的接口等性质,所以类还需要设计容器来存放这些属性
1.3.1.2 接口设计
接口还具有继承的接口这一性质
1.3.1.3 方法设计
方法唯一具有的关机是包含关系,其会具有传入参数和返回值
1.3.2 顺序图架构设计
顺序图的设计重点则是包含与消息
Collaboration -包含-> Interaction
Collaboration -包含-> Attribute
Interaction -包含-> Lifeline
Interaction -包含-> EndPoint
Interaction -包含-> Message
LifeLine -Message-> EndPoint
LifeLine -Message-> LifePoint
EndPoint -Message-> EndPoint
EndPoint -Message-> LifePoint
LifeLine 和 EndPoint 都可以作为消息的起始/终点,其需要具有一个公共的父类或接口
而Message会区分发送与接收的信息存入这一公共的接口中。
1.3.2.0 公共父类设计
LifeLine和EndPoint的共性只有ID和消息两种
1.3.2.1 LifeLine设计
LifeLine除了具有父类的特性外,还具有创建着这一属性
1.3.2.2 EndPoint
EndPoint没有别的性质
1.3.3 状态图架构设计
状态图实际上是一个有向图结构,其节点为State,边为Transition,而边上带有事件和副作用
所以状态图设计为一个只具有包含结构的图结构,重写State相关的类,加入出边容器和入边容器,其储存的对象为重写的转移类
重写的转移类中具有触发事件与副作用
1.5 第二阶段需求与设计
1.5.1 R001-元素名字为空
在属于类图的元素中除了以下几种元素之外,其余元素的name
字段均不能为空
- UMLParameter(Direction:Return)
- UMLAssociation
- UMLAssociationEnd
- UMLGeneralization
- UMLInterfaceRealization
实现思路
获取所有的UmlElement,对上述五种进行单独处理,遍历其余各种UmlElement,检查姓名是否为空。
1.5.2 R002-属性名字重复
在一个类中,其所有的Attribute和Association另一端End所构成的元素集合中,不能出现重名元素
实现思路
通过一个HashMap<String,Integer>容器,对于所有的名字进行统计,在完成统计后,找出容器中所有出现次数大于1的名字,其即为重复属性的名字。
1.5.3 R003-循环继承
类或接口的继承关系中存在循环继承的情型。
实现思路
对于类而言,其仅能进行单继承,所以所有类构成的继承图仅可能为若干环和若干链作为分支的图。可以使用链表判环算法来检查其是否有环。
但是对于接口而言,其具有多继承的能力,其构成的继承图就是一个普通的图。需要对于每一个接口使用dfs来判环
同时,需要由工厂类给出支持,穿出所有的类和所有的接口的容器。
1.5.4 R004-重复继承
类或接口(直接或间接地)重复继承了同一个类或接口
实现思路
首先明确,类由于单继承限制,是不可能出现重复继承的情况的,唯一可能的情况是某一父类继承了自己,但是在进行R003检查时被检查出来,不会进入R004
所以对于重复继承来说,只需要对于接口进行重复继承的检查。
重复继承的检查,采用bfs遍历,如果在bfs遍历的过程中,检查到了重复的接口,那这一接口便存在重复继承的情况。
在检查的过程中,需要维护一个HashSet的引用,保证对于每一个接口,只进行一次检查。
1.5.5 R005-接口不可见
所有接口的属性都需要是public
实现思路
对于所有的接口元素进行检查,如果属性不为Public,即抛出异常
1.5.6 R006-LifeLine引用不同Sequence属性
每一个LifeLine引用的Attribution都需要是同一Sequence内定义的
实现思路
- 需要在工厂类中将Attribute加入Lifeline中引用。
- 获取LifeLine的实体,检查Represent的Attribute的ParentID和Lifeline的ParentID是否相同。
1.5.7 R007-LifeLine在销毁后收到消息
Lifeline不能在Delete消息之后在收到消息
实现思路
对于每一个LifeLine,遍历其收到的消息列表,当检查到第一个销毁信息时,判断此时是否已经到达消息列表最后一个,如果不是,则代表在被销毁后收到了消息
1.5.8 R008-FinalState存在迁出
FinalState不能有迁出行为
实现思路
获取到所有的FinalState,检查其toTransition的数目
1.5.9 R009-状态具有两个相同条件的迁出
同一个状态的不能有条件相同的迁出
- 如果存在一个状态的所有的迁出 Transition 的所有触发事件(Trigger)中有两个
name
字段字符串相同的 Trigger,那么这两个 Trigger 所在的 Transition 必须拥有不为空 且逻辑上不能String.equal()成立的守护条件(Guard);
实现思路
首先需要获取所有的状态
对于所有的状态的迁出Transition进行遍历,使用O(n2)的方法来一一对比
二、设计思路的演变
2.1 第一单元
在本学期的第一次作业中,我就体会到了设计的重要性。
在完成第一次作业时,并没有对与先对作业架构进行设计,而是直接上手开始编码,导致编码的思路一直是最先想到的思路,没有得到优化改进。糟糕的架构和思路导致代码非常复杂,同时在编写的过程中,由于处于边写边构思的状态,所以在编码时效率低下且容易出错,每次发现一个问题,就需要进行局部重构。第一次作业完成时代码量很大,但是其中有很多冗杂的部分。
但是第一次作业的效果并不好,在强测、互测中都被Hack了很多次,体现了屎山的不易维护性。
第二次作业我就选择了全体重构,同时面对下发的需求,先进行设计的构思并撰写文档,完成后再开始编码。
先对需求分析并进行设计后,效率得到了很大的提升。第一次作业虽然拿到需求就开始编码,但编码的过程足足消耗了3天半的时间。而先对需求分析和设计之后,虽然分析设计的过程需要大约一天的时间,但是面对已经构建好的框架进行编码,同样也只需要一天左右的时间就可以完成。
从第二次作业开始,我就遵循了先分析构思,再编码实现的流程。
2.2 第二单元
在第一单元中总结出的先分析构思,再编码实现的流程在第二单元中也同样的好用,但是在第二单元中暴露出来分析构思环节中存在的问题。
在第一单元中,我采用的构思仅仅触及到架构的层面,分析所涉及到的数据,根据其归属和操作对其分类,并将其封装为多个对象,再根据对象之间的关系来决定类的组织。完成到这一步构思部分就结束了。而对于每个类之中的方法的实现,仍然是在编码的时候再决定。
在第二单元中,这一因素就导致了我的电梯调度算法,是在所有架构的编码都完成后,最后再来对函数进行的补全。由于在设计时并没有考虑算法的适应性,架构的限制导致了有的算法实现起来并不容易,在实现对这一架构适配的LOOK算法的过程中出现了失误。第二单元的三次作业每一次都会在强测中出现一个点的超时。
2.3 第三单元
在第三单元中并不需要对架构进行太多的设计,但是这一单元需要对一些算法进行设计,结合上一单元的经验,这一单元将具体需要实现的算法也加入了设计的部分。同时在对拍器的帮助下,实现了第三单元的无伤通过
三、课程收获
本学期的OO课程给我最大的收获,就是对于一个较为复杂的需求进行解构、连接、逐个击破的能力和实践。
在这个学期之前,接触的代码,都是分散和小量级的代码,并没有模块和耦合这一说法,同时这些代码只写一次,并不会有增量开发的需求。
但是在oo中就不一样了,每次作业都是一个小的系统,需要自行设计一些模块去构建,还需要控制模块间的耦合度,使在增量开发的时候最小化的最模块内部进行修改。
每一次作业的体验可以说都是全新的。
四、建议
- 希望能在作业中加入一些鲁棒性的内容和考察,例如在第三、四次作业中的官方包对于非法输出的实现。
- 希望在每一次作业的提示部分中,增加一些对于设计模式的建议和应用提示。
- 希望能引入一些开发流程相关的内容。