BUAA OO Unit4
文章目录
基础知识
UML类图
类(Class)在类图中的表示方式
以课程组PPT中的实例为例:
- UML类图中表示可见性的符号有3种:
- + :表示public
- - :表示private
- #:表示protected
- ~:表示friendly
- 属性(Attibute)的表示方法:
可见性 名称:类型[= 缺省值]
,其中[ ]内内容可省略- 例如:
- balance:real[= 0]
- 操作(Operations)的表示方法:
可见性 名称(参数列表) [:返回类型]
+addAcount(a: Acount)[:void]
接口(Interface)
接口是一种特殊的类:
- 它具有类的结构但不可被实例化,只可以被子类实现。
- 它包含抽象操作,但不包含属性。
- 它描述了类或组件对外可见的动作。
- 在 UML 中,接口使用一个带有名称的小圆圈来进行表示。
类与类之间关系的表示方式
根据类与类之间的耦合度从弱到强排列,UML 中的类图有以下几种关系:依赖关系、关联关系、聚合关系、组合关系、泛化关系和实现关系。其中泛化和实现的耦合度相等,它们是最强的。
依赖(Dependency)
依赖(Dependency)关系是一种使用关系,它是对象之间耦合度最弱的一种关联方式,是临时性的关联。在代码中,某个类的方法通过局部变量、方法的参数或者对静态方法的调用来访问另一个类(被依赖类)中的某些方法来完成一些职责。在 UML 类图中,依赖关系使用带箭头的虚线来表示。
关联(Association)
关联可以是双向的,也可以是单向的。在 UML 类图中,双向的关联可以用带两个箭头或者没有箭头的实线来表示,单向的关联用带一个箭头的实线来表示,箭头从使用类指向被关联的类。也可以在关联线的两端标注角色名,代表两种不同的角色。
在代码中通常将一个类的对象作为另一个类的成员变量来实现关联关系。图 5 所示是老师和学生的关系图,每个老师可以教多个学生,每个学生也可向多个老师学,他们是双向关联。
聚合(Aggregation)
聚合关系也是通过成员对象来实现的,其中成员对象是整体对象的一部分,但是成员对象可以脱离整体对象而独立存在。例如,学校与老师的关系,学校包含老师,但如果学校停办了,老师依然存在。
在 UML 类图中,聚合关系可以用带空心菱形的实线来表示,菱形指向整体。图 6 所示是大学和教师的关系图。
组合(Composition)
在组合关系中,整体对象可以控制部分对象的生命周期,**一旦整体对象不存在,部分对象也将不存在,部分对象不能脱离整体对象而存在。**例如,头和嘴的关系,没有了头,嘴也就不存在了。
在 UML 类图中,组合关系用带实心菱形的实线来表示,菱形指向整体。图 7 所示是头和嘴的关系图。
泛化(Generalization)
泛化(Generalization)关系是对象之间耦合度最大的一种关系,表示一般与特殊的关系,是父类与子类之间的关系,是一种继承关系,是 is-a 的关系。
在 UML 类图中,泛化关系用带空心三角箭头的实线来表示,箭头从子类指向父类。在代码实现时,使用面向对象的继承机制来实现泛化关系。例如,Student 类和 Teacher 类都是 Person 类的子类,其类图如图 8 所示。
实现(Realization)
实现(Realization)关系是接口与实现类之间的关系。在这种关系中,类实现了接口,类中的操作实现了接口中所声明的所有的抽象操作。
在 UML 类图中,实现关系使用带空心三角箭头的虚线来表示,箭头从实现类指向接口。例如,汽车和船实现了交通工具,其类图如图 9 所示。
UML状态图
概念
状态图(Statechart Diagram)是描述一个实体基于事件反应的动态行为,显示了该实体如何根据当前所处的状态对不同的事件做出反应。
一般用状态机对一个对象的生命周期建模,重点在与描述状态图的控制流。
状态(States)
状态用圆角矩形表示。
初态和终态(Initial and Final States)
初态用实心圆点表示,终态用圆形内嵌圆点表示。
转移(Transitions)
转移(Transitions)是两个状态之间的一种关系,表示对象将在源状态(Source State)中执行一定的动作,并在某个特定事件发生而且某个特定的警界条件满足时进入目标状态(Target State)。
触发器(Trigger)
转移的诱因,可以是一个信号,事件、条件变化(a change in some condition)和时间表达式。
监护条件(Guard)
当警界条件满足时,事件才会引发转移。
效果(Effect)
对象状态转移后的效果。
格式:触发[监护条件]/效果
状态动作(State Actions)
状态动作(Actions)是一个可执行的原子操作(动作是不可中断的,其执行时间是可忽略不计的)
在上例中,对象状态转移后的结果显示在转移线上,如果目标状态有许多转移,而且每个转移有相同的结果,这时把转移后的结果(Effect)展示在目标状态中(Target State)更好一些,可以定义进入动作(Entry Action )和退出动作(Exit Action)。
UML顺序图(即时序图)
对象(Object)
对象位于时序图的顶部,以一个矩形表示。对象的命名方式一般有三种:
- 对象名和类名。例如:华为手机:手机、loginServiceObject:LoginService。
- 只显示类名,不显示对象,即为一个匿名类。例如::手机、:LoginSservice。
- 只显示对象名,不显示类名。例如:华为手机:、loginServiceObject:。
生命线(LifeLine)
时序图中每个对象和底部中心都有一条垂直的虚线,这就是对象的生命线(对象的时间线)。以一条垂直的虚线表示。
控制焦点(Activation)
控制焦点代表时序图中在对象时间线上某段时期执行的操作。以一个很窄的矩形表示。
消息(Message)
表现代表对象之间发送的信息。消息分为三种类型。
同步消息(Synchronous Message)
消息的发送者把控制传递给消息的接收者,然后停止活动,等待消息的接收者放弃或者返回控制。用来表示同步的意义。以一条实线+实心箭头表示。
异步消息(Asynchronous Message)
消息发送者通过消息把信号传递给消息的接收者,然后继续自己的活动,不等待接受者返回消息或者控制。异步消息的接收者和发送者是并发工作的。以一条实线+大于号表示。
返回消息(Return Message)
返回消息表示从过程调用返回。以小于号+虚线表示。
自关联消息
表示方法的自身调用或者一个对象内的一个方法调用另外一个方法。以一个半闭合的长方形+下方实心剪头表示。
常见组合片段
抉择(Alt)
抉择在任何场合下只发生一个序列。 可以在每个片段中设置一个临界来指示该片段可以运行的条件。else 的临界指示其他任何临界都不为 True 时应运行的片段。如果所有临界都为 False 并且没有 else,则不执行任何片段。Alt片段组合可以理解为if…else if…else条件语句。
选项(Opt)
包含一个可能发生或不发生的序列。Opt相当于if…语句。
循环(Loop)
片段重复一定次数,可以在临界中指示片段重复的条件。Loop相当于for语句。
并行(Par)
并行处理,片段中的事件可以并行交错。Par相当于多线程。
第一次作业
第一次作业要求实现UML类图的解析器。
拿到作业最开始需要厘清这个单元需要我们做什么——官方包的文件过于繁多,需要找到关键信息,也就是model包中存的各个类。这些类对应的是类图中的元素,而我们根据样例的内容,可以判断这些类之间的关系,及这些类的功能,进而建立一个完整的架构体系,并根据这一体系实现相应的功能。具体介绍如下。
架构:
首先,根据样例判断出本次作业的层次:
实现:
结合作业要求实现的方法,发现需要在class、operation
这两个层次实现相关的方法。除了Main
和MyUmlInteration
外,新建两个类MyUmlClass
和MyUmlOperation
。
解析时,首先建立elementHashMap
,其类型为HashMap<ElementTypes,HashMap<String,UmlElement>>
,用于按照元素类型分类,存储所有元素。又因为新建了MyUmlClass和MyUMLOperation这两个类,因此新建了classes
和operations
两个Hashmap,存储了对应的元素。
之后的各个方法根据要求实现即可,需要注意诸多细节,例如异常抛出的先后顺序、涉及继承关系的方法是否包含类自身等等。
优化:
可以设置缓存机制,将需要递归遍历的元素存在类本身,从而减小时间复杂度。例如将类实现的接口存在类自身中,如果已经查询过则直接返回而不需要二次查询。
if (classes.get(child).isHasCheckInterface()) {
return classes.get(child).getInterfaces();
}
UML类图:
第二次作业
本次作业要求在上次作业基础上,扩展解析器,使得能够支持对UML顺序图和UML状态图的解析。
架构:
再次判断各种元素之间的层次:
实现:
因为本次作业中未涉及Collaboration,所以对于时序图,考虑Interaction为顶层元素。根据父子间的关系建立了Interaction与Lifeline、message之间的联系。
对于状态图,因为StateMachine和Region是一一对应的关系,因此将需要实现的方法都放在Region类中。
关于抛出的异常,因为时序图和状态图需要实现的几个方法都首先需要在UMLGeneralInteraction
类找到对应的元素,因此在“找元素”的过程中即可抛出异常。
public String getInteractionId(String iname)
throws InteractionNotFoundException, InteractionDuplicatedException {
boolean flag = false;
String answer = null;
for (UmlElement e : elementTypes.get(ElementType.UML_INTERACTION)) {
String id = e.getId();
String name = interactions.get(id).getInteraction().getName();
if (name.equals(iname)) {
if (!flag) {
answer = id;
flag = true;
} else {
throw new InteractionDuplicatedException(iname);
}
}
}
if (!flag) {
throw new InteractionNotFoundException(iname);
}
return answer;
}
找StateMachine的ID同理。
优化:
刚刚写博客的时候想到,既然StateMachine和Region是一一对应的,那么在这个单元其实可以不实现StateMachine类而直接实现Region类。
UML类图:
第三次作业
本次作业要求在上次作业基础上,对模型进行有效性检查。
架构和上次作业几乎一样,不同点在于UMLAttribute
的父亲可能是UMLClass/UMLInteraction/UMLCollaboration
,因此在对该元素归类时需要区分其父类。
同时对于R005和R006,设置全局变量,在最开始对元素进行归类时即判断是否出错更为简便。
设计架构及对OO方法的理解演进
如果把假期的预习作业视为第零单元的话,OO陪我们走过了五个单元。
第零单元主要考察较为基础的语法,让我们初步掌握了JAVA的语法规则,知道了一些常见的容器,为之后的学习打下基础。也正是这一单元,让我对OO的“面向对象”思想有了初步的认识。犹记当时的作业涉及“对象”这个概念,我又老记不住,就翻到讲解概念的那一节,反复看,终于是简单理解了什么是“对象”,什么又是对象的属性、方法。
到第一单元,考察表达式求导。第一次作业非常基础,只需要按规则对表达式进行求导并整合即可,但作为崭新的第一个java作业,从零开始,到一步步搭建自己的结构,确实不容易,最后搭建出了成果也确实令人振奋。可能是因为第零单元打下的基础还不错,我在这时候已经能把“面向对象”的思想和“面向过程”的思想较为明确地区分开了。而之后第二三次作业难度的逐渐加深和过渡,则是让我在理解“面向对象”思想的基础上,了解到了不同对象的层次。自顶而下还是从下往上,因子、项、表达式怎么组合,不同的对象怎么进行交互····这些,是在对象之上的一层理解:架构。
第二单元考察电梯载人。第一次了解到“多线程”,和第零单元了解到对象一样,都是一脸懵逼。狠狠地看了好多博客后慢慢理解了什么是多线程的思想,什么又是观察者模式、生产者消费者模式等等。这可能体现了OO的一个重要思想:合理设计对象间的交互。在这其中,安全性是第一位,其次也要考虑怎样是高效的。记得当时查了很多算法,最后自己捣鼓出的大概是各个算法的混杂体吧。
第三单元设计的是JML。嗯。。其实个人认为这一单元没有太过强调面向对象的思想,而是向我们展示了OO的规范性。仔细理解每一个JML规格,不放过每一个错漏点,才能完成最后的测试。这一单元我狠狠地理解了“细节决定成败”。同时,这一单元也在一定程度上考察了算法——JML规格是固定的,但实现却是千差万别。在正确的基础上实现高效,是我们从这一单元中应该学习的。
刚刚结束的第四单元考察UML。其实早在最开始的博客作业即对UML有一定要求,只是当时没重视()。现在重新学习,才发现这一单元的“层次化”。代码、UML图、UML语言描述,三者之间的相互映照,以及不同类型的UML图之间的对应,都值得我们仔细思考。此外,了解了UML之间的层次化后,如何将其“模型化”,也是一大难点。
对测试理解和实践的演进
- 评测机:主要针对一单元,因为它有固定规则和固定结果。优点:只要评测机靠谱,即简单又方便。缺点:评测机生成逻辑也依赖写评测机的人,所以如果本来就理解错了那就没办法了()
- 对拍:对拍好,对拍妙,对拍呱呱叫!其实就相当于没有标准答案的评测机,优点显然,缺点是如果大家都理解错了那就没办法了。。。
- JUnit测试:针对单个方法测试,自己用的很少所以不是很了解。
- 手动构造:分为两类:基础数据和边缘数据。基础数据是看基本的方法实现是否正确,边缘数据除了正确性也考察性能。优点显然缺点一是累二是边缘数据真的不好造啊。
自己的课程收获
- 了解了面向对象思想,掌握了一些搭建框架的能力。
- 会写一点点Java代码了。
- 会写一点点评测机了(真的是一点点)。
- 会画一点点UML图了(虽然大概率会出好多错)。
- 知道了一点点多线程思想和实现方法。
- 知道了一些Java常见的模式和一些基础算法。
- 磨砺了自己可怜弱小又无助的心灵。
- 体验很好,明年不来()。
对OO课程的建议
-
在每单元的作业下发前铺垫更多的理论内容。
每个单元其实都带着一份新知识点来,每次面对新单元要学习的东西真的很多而OO作业的时间也有点紧张所以如果每次作业前都能有一些更为“落地”的讲解可能更好一些。比如面对多线程时,我除了它的“名词解释”之外完全不知道其他,这个过程是怎么样的、落实到代码上怎么实现··· 都是后来在上网查资料、和同学的交流、以及好心助教在讨论区进行讲解才一点点清楚起来。但如果最开始就能有一个“方向性”的指导我觉得这个过程会轻松很多。
-
合理规划每个单元的难易程度。
我至今记得写hw2的那个周六,凌晨两点自己写不出来趴在桌子上痛哭了一会儿后抬起头咬牙继续写的痛苦。。。虽然是因为我蒻蒟但真的那次作业难度跨度不会太大了嘛www。相较于一二单元,三单元确实较为简单,可能这和单元本身的属性有关,但如果能更为合理地规划每次作业设置的难度或者时间或许会好一些。
当然,如果OO是和OS相互规划,当我没说 -
互测的价值有待思考
互测的出发点自然是好的。但是:第一,因为OO和OS安排时间的原因,互测的时间段(至少本蒻蒟)几乎没有时间去看大家的代码而是在复习OS,不知道有没有类似情况的同学。。第二,存在一些恶性hack的情况,比如在指导书、强测都没要求的情况下去hack算法时间,虽然这也能变相鞭策大家优化算法但着实有点小无语。此外,互测会不会导致大家更为注重测试而非实现代码本身?以及如果对互测量有强制要求,像我说的第一点就比较无奈。
其他的以后想到了补充。