第四单元总结性博客

本单元的架构设计

MyImplementation构造函数

在构造函数中,依照UMLElement的类型的顺序进行添加(而不是直接拿着elements数组进行forEach遍历)。

这样的好处是,在添加Element时,其parentId对应的UMLElement一定存在,从而可以更好的对数据进行管理。

// 类图
Arrays.stream(elements).filter(e -> e.getElementType().equals(ElementType.UML_CLASS))
    .forEach(c -> myImplementation.addClass((UmlClass) c));
Arrays.stream(elements)
    .filter(e -> e.getElementType().equals(ElementType.UML_INTERFACE))
    .forEach(i -> myImplementation.addInterface((UmlInterface) i));
Arrays.stream(elements)
    .filter(e -> e.getElementType().equals(ElementType.UML_ATTRIBUTE))
    .forEach(a -> myImplementation.addAttribute((UmlAttribute) a));
Arrays.stream(elements)
    .filter(e -> e.getElementType().equals(ElementType.UML_OPERATION))
    .forEach(o -> myImplementation.addOperation((UmlOperation) o));
Arrays.stream(elements)
    .filter(e -> e.getElementType().equals(ElementType.UML_PARAMETER))
    .forEach(p -> myImplementation.addParameter((UmlParameter) p));
Arrays.stream(elements)
    .filter(e -> e.getElementType().equals(ElementType.UML_GENERALIZATION))
    .forEach(g -> myImplementation.addGeneralization((UmlGeneralization) g));
Arrays.stream(elements)
    .filter(e -> e.getElementType().equals(ElementType.UML_INTERFACE_REALIZATION))
    .forEach(ir -> myImplementation.addInterfaceRealization((UmlInterfaceRealization) ir));
Arrays.stream(elements)
    .filter(e -> e.getElementType().equals(ElementType.UML_ASSOCIATION_END))
    .forEach(ae -> myImplementation.addAssociationEnd((UmlAssociationEnd) ae));
Arrays.stream(elements)
    .filter(e -> e.getElementType().equals(ElementType.UML_ASSOCIATION))
    .forEach(a -> myImplementation.addAssociation((UmlAssociation) a));
// 状态图
Arrays.stream(elements)
    .filter(e -> e.getElementType().equals(ElementType.UML_STATE_MACHINE))
    .forEach(c -> myImplementation.addStateMachine((UmlStateMachine) c));
Arrays.stream(elements)
    .filter(e -> e.getElementType().equals(ElementType.UML_REGION))
    .forEach(c -> myImplementation.addRegion((UmlRegion) c));
Arrays.stream(elements).filter(e -> e.getElementType().equals(ElementType.UML_STATE))
    .forEach(myImplementation::addState);
Arrays.stream(elements)
    .filter(e -> e.getElementType().equals(ElementType.UML_FINAL_STATE))
    .forEach(c -> myImplementation.addFinalState((UmlFinalState) c));
Arrays.stream(elements).filter(e -> e.getElementType().equals(ElementType.UML_PSEUDOSTATE))
    .forEach(myImplementation::addState);
Arrays.stream(elements)
    .filter(e -> e.getElementType().equals(ElementType.UML_TRANSITION))
    .forEach(c -> myImplementation.addTransition((UmlTransition) c));
Arrays.stream(elements)
    .filter(e -> e.getElementType().equals(ElementType.UML_EVENT))
    .forEach(c -> myImplementation.addEvent((UmlEvent) c));
// 顺序图
Arrays.stream(elements)
    .filter(e -> e.getElementType().equals(ElementType.UML_INTERACTION))
    .forEach(c -> myImplementation.addInteraction((UmlInteraction) c));
Arrays.stream(elements)
    .filter(e -> e.getElementType().equals(ElementType.UML_LIFELINE))
    .forEach(c -> myImplementation.addLifeline((UmlLifeline) c));
Arrays.stream(elements)
    .filter(e -> e.getElementType().equals(ElementType.UML_ENDPOINT))
    .forEach(c -> myImplementation.addEndpoint((UmlEndpoint) c));
Arrays.stream(elements)
    .filter(e -> e.getElementType().equals(ElementType.UML_MESSAGE))
    .forEach(c -> myImplementation.addMessage((UmlMessage) c));

对一些UMLElement进行扩展

例如,针对UMLClass这个UMLElement,我编写了一个MyClass类,以“组合”的方式对UMLClass进行复用,并增加了很多成员属性从而能够通过这个对象获取与该UMLClass有关的更多信息。

在各种MyElement(即我对于UMLElement的复用扩展)中加很多HashMap(我爱HashMap!),建立更多的联系。这样可以更方便的对整个UML结构进行更加高效的查询。

public class MyClass {
    private UmlClass umlClass;
    private final HashMap<String, HashMap<Visibility, HashMap<String, MyOperation>>> n2vis2id2oper
        = new HashMap<>();
    private final HashMap<String, UmlAttribute> id2attr
        = new HashMap<>(); // 所有的attribute
    private final HashMap<String, UmlAttribute> id2refAttr
        = new HashMap<>(); // 所有的referenceType的Attr

    private final HashMap<String, MyAssociationEnd> id2ae = new HashMap<>();
    private final HashMap<String, MyClass> id2sonClass = new HashMap<>(); // 可能有多个子类
    private final HashMap<String, MyInterface> id2impleInterface = new HashMap<>(); // 可能实现了多个接口
    
    private MyClass fatherClass = null; // 单继承;最多只会有一个父类
}

把一些独立的功能进行封装

例如,在getStateIsCriticalPoint这一方法中,需要利用dfs对所有State们进行遍历。就可以将dfs的部分从getStateIsCriticalPoint方法中拿出来。

public boolean getStateIsCriticalPoint(String stateMachineName, String stateName)
    throws StateNotFoundException, StateDuplicatedException {
    ...
    {
        ...
        MyDfs myDfs;
        if (noCriticalPoint == 0) {
            myDfs = new MyDfs(id2stateAll, id2finalState);
        if (myDfs.dfs(initialState.getId()) == 0) {
            // 如果状态机模型本来就无法从 Initial State 到达任意一个 Final State,
            // 则该状态机中所有状态都不是关键状态
            noCriticalPoint = 1;
            return false;
        } else {
            noCriticalPoint = -1; // 表示可能存在关键状态
        }
    }
    ..
    // 将state移除,看是否dfs结果为0。
    myDfs = new MyDfs(id2stateAll, id2finalState);

    // 若为0,则state为关键状态
    return myDfs.dfs(initialState.getId(), state.getId()) == 0;
}

在编写代码中出现的bug

U4T2

一个Endpoint到一个Lifeline的Message可能有很多个。所以,不能只存Endpoint的id。(如果只存Endpoint的id的话,那在执行PTCP_LOST_AND_FOUND Interaction2 Lifeline1询问的时候,就可能会得到比正确答案更小的结果。)

一个Lifeline可能接收到一个Lifeline的多条create message。所以,不能只存creator的Lifeline的id。(只存creator的Lifeline的id的话,在执行PTCP_CREATOR Interaction1 Lifeline3询问的时候 ,本应因抛出LifelineCreatedRepeatedlyException而输出fail信息,但可能能正常输出ok的查询信息。)

U4T3

在addMessage的时候,不能只是把键值对id, UmlMessage添加到HashMap中,因为可能会出现如下情况:

image-20220629133053160

在这种情况下,应该报R004异常。所以,需要将每一个UMLInterface的直接父类的继承次数也进行记录。

在四个单元中架构设计思维及OO方法理解的演进

U1

在本单元中,我开始熟悉“层次化设计”的理念。利用“递归下降法”的思路,将“表达式解析”这一整个复杂的问题,逐层拆分、拨繁就简。将指导书中繁杂的概念定义以形式化表述的方式呈现,将解析“表达式”的任务转化为解析项,再从解析项转化为解析因子...... 而对每一种Element的解析都可以封装到一个类里(当然,还可以通过实现Parsable接口的方式让代码结构更加清晰)。

形式化表述

  • 表达式 $\rightarrow$ 空白项 [加减 空白项] 项 空白项 | 表达式 加减 空白项 项 空白项
  • 项 $\rightarrow$ [加减 空白项] 因子 | 项 空白项 '*' 空白项 因子
  • 因子 $\rightarrow$​ 变量因子 | 常数因子 | 表达式因子
  • 变量因子 $\rightarrow$ 幂函数 | 三角函数 | 自定义函数调用 | 求和函数
  • 常数因子 $\rightarrow$​ 带符号的整数
  • 表达式因子 $\rightarrow$​ '(' 表达式 ')' [空白项 指数]
  • 幂函数 $\rightarrow$ (函数自变量|'i') [空白项 指数]
  • 三角函数 $\rightarrow$ 'sin' 空白项 '(' 空白项 因子 空白项 ')' [空白项 指数] | 'cos' 空白项 '(' 空白项 因子 空白项 ')' [空白项 指数]
  • 指数 $\rightarrow$ '**' 空白项 ['+'] 允许前导零的整数
  • 带符号的整数 $\rightarrow$ [加减] 允许前导零的整数
  • 允许前导零的整数 $\rightarrow$ (0|1|2|…|9)
  • 空白项 $\rightarrow$
  • 空白字符 $\rightarrow$ (空格) | \t
  • 加减 $\rightarrow$ '+' | '-'
  • 自定义函数定义 $\rightarrow$ 自定义函数名 空白项 '(' 空白项 函数自变量 空白项 [',' 空白项 函数自变量 空白项 [',' 空白项 函数自变量 空白项]] ')' 空白项 '=' 空白项 函数表达式
  • 函数自变量 $\rightarrow$ 'x' | 'y' | 'z'
  • 自定义函数调用 $\rightarrow$ 自定义函数名 空白项 '(' 空白项 因子 空白项 [',' 空白项 因子 空白项 [',' 空白项 因子 空白项]] ')'
  • 自定义函数名 $\rightarrow$ 'f' | 'g' | 'h'
  • 求和函数 $\rightarrow$ 'sum' '(' 空白项 'i' 空白项',' 空白项 常数因子 空白项 ',' 空白项 常数因子 空白项 ',' 空白项 求和表达式 空白项 ')'
  • 函数表达式 $\rightarrow$ 表达式
  • 求和表达式 $\rightarrow$ 因子

U2

除了对于多线程相关概念的理解,这一单元中对于一些OO设计基本原则(例如单一职责原则、合成复用原则、开闭原则等等),设计模式(例如:工厂模式、访问者模式)的介绍让我们对于OO思想有了更深入的理解。

U3

本单元主要是训练对于规格的理解和认识。将视角更多的聚焦于针对单个方法的正确性的检验,而非对于整个功能性的测试。同时,对于JUnit单元测试,我也觉得这种方式很实用。

U4

这一单元聚焦于对于UML的解析。虽然UML对一些面向对象的项目代码结构有很好的描述作用(UML类图、顺序图等等),但是本单元的编程中,我个人感觉对于面向对象设计并不多。对于此单元的详细解读,详见本单元的架构设计

在四个单元中测试理解与实践的演进

主要原则:随机生成数据轰炸+手动构造

随机生成数据轰炸

用大量的数据、广泛的样例尽可能的更加广泛的覆盖到可能的输入情况,并以此来检验程序的正确性。

手动构造

这是作为随机生成数据的补充,用于对随机生成数据难以覆盖到的边界点进行检验。

  • U1

    • 构造x*x*x*x*x....这种样例测试程序是否会栈溢出。
  • U2

    • 针对不同环形电梯运输方向、乘客请求方向、乘客分布的测试样例
  • U3

    • 构造极端的图和重复几百次的某指令,从而实现针对特定方法的复杂度的检验
  • U4

    • 测试某些指令是否实现记忆化(不实现的话会出现TLE)

    • 例如:

    • 指令 7:类实现的全部接口

      输入指令格式:CLASS_IMPLEMENT_INTERFACE_LIST classname

      举例:CLASS_IMPLEMENT_INTERFACE_LIST Taxi

      输出:

      • Ok, implement interfaces of class "classname" are (A, B, C).
        • 此例中,类 classname 实现了 ABC 这 3 个接口;
        • 无论是直接实现还是通过父类或者接口继承等方式间接实现,都算做实现了接口;
        • 传出列表时可以乱序,官方接口会自动进行排序(但是需要编写者自行保证不重不漏);
        • 如果类 classname 没有实现任何接口,则传出一个空列表;
      • Failed, class "classname" not found.
        • 不存在名为 classname 的类时,输出上述内容;
      • Failed, duplicated class "classname".
        • 存在多个名为 classname 的类时,输出上述内容。
    • 图为关于时间复杂度的分析(即需要记忆化的必要性)image-20220629142450906

课程收获

  • Java SE
  • 面向对象的编程思想
  • 新结识的很多小伙伴
  • 自己自信心的回复(曾经怀疑自己还有没有学习能力了..)

这部分,再怎么总结,也不及这一学期的所有投入、随之起伏的心情来得实在。行文至此,我刚刚得知OO助教招聘落选,心有不甘的同时,我也决心要去更加的努力的向未来的学习生活迈进!因此,我也感谢OO课的老师、助教们给我的激励!

给课程提三个具体的改进建议

以下这些内容是我在应聘OO助教时的想法,在此重新整理一下。衷心希望OO课程越来越好!

OO网站的设计

网站页面设计

  1. oo网站界面的设计不太合理。预习、训练、作业、实验这几个部分应该是并列的,但是在oo网页上的逻辑布局不太合理。

讨论区

  1. 讨论区的设计有点不人性化。同学们发布的帖子最好开放删除、编辑等权限。
  2. 讨论区最好增加“在当前楼的”回复功能。这样,尤其是在答疑帖里面,同学们在帖字内的问的内容和助教解答会更加清晰明了。
  3. 订阅一个讨论帖后,当自己在该帖下面评论时,也会收到通知信息(这个不太符合逻辑,就像你在你的朋友圈下面点赞评论后,微信又冒出红点,然后把你刚刚的点赞评论的内容又复读机似的告诉了你一遍)
  4. 最好可以查看自己的历史讨论帖和所有订阅过的帖子。

互测

  1. 互测时,如果提交的hack样例不合法,评测机最好可以给予更具体的反馈。不然要么同学们就还会在群里面问,还得增加助教很多不必要的工作量;要么就是评测机出错了,搞得同学还得琢磨半天这样例哪里不合法了)
  2. 互测时,最好可以设置一个hack样例提交队列。如果我有5个hack样例,可以直接都提交上去,冷却时间每次一到,就自动取出队列中一个样例提交进行评测。给每隔15分钟都需要打开一次页面进行样例提交的同学减轻一点负担。

bug修复

  1. 非合并修复的存在意义不大。我觉得可以取消非合并修复。直接要求同学们每次修复时手动填入本次修复的bug数量n。如果n小于通过本次commit成功修复的bug数量且修改的行数超过5*n,则需要附上修改记录说明供助教审核。

研讨课的效率

主要的问题:讨论后的半节课时间每组派一名代表给大家分享的过程中,上面的同学分享的大同小异、自说自话,下面的同学听得不认真。后半节课(9点到9点35)的效率低下,宝贵的课时有点浪费。

究其原因,是大家在讨论的过程中,准备时间仓促,虽说组员的思路很发散(天马行空)有助于大家互相进步,但是在每组代表上去分享的过程中,没有较为集中的中心主题,讲得泛泛、略于表面而不深入。且大家各组讨论和分享的内容是相同的,这就会导致到了分享环节,台下听得同学们会觉得这些个问题我们刚刚已经都讨论过了,就没有太大的必要再去听了。

解决办法:

  1. 让每个小组讨论的不一样的主题。
  2. 每个小组课后的总结最好以博客等形式分享给所有的同学,让大家分享的内容落在实处(而非在课堂上随着话音转瞬即逝、随风而散了)。甚至可以考虑同学互评的方式评选内容质量较高的总结博文,从而带动同学们的积极性(当然,这样可能又会增加大家的负担)。
  3. 课时分配:可以考虑单周课全部用来讨论,单周课来做分享。让同学们有充分的准备时间。

微信群和讨论区相分离

微信群和讨论区分离:同学只在微信群问,助教在讨论区精华帖里汇总。(这一点跟很多同学/助教的看法不一样,我并不太赞成同学们“七嘴八舌”的问题都放在讨论区的)

学生:互帮互助!让同学们的问题都发布在微信群,鼓励同学们互相帮助(助教是有限的,同学们是无限的)。学生在提问前先浏览讨论帖,观察是不是该问题已经被解答。

助教:有求必应!让有困难的同学感受到家的温暖~ 不仅是对问题的回答,还是对躁动心灵的安抚~

posted @ 2022-06-29 15:32  wlc000  阅读(21)  评论(0编辑  收藏  举报