OO第三单元总结

面向对象JML系列------第三单元

JML

注释格式

单行注释://@annotation
多行注释:/*@ annotation @*/

表达式格式

JML表达式与Java表达式十分相似,就是多了一些操作符、原子表达式

原子表达式

\result

一个方法(返回值不是void)的返回值

\old(expr)

expr在执行该方法前的取值
对象引用:只判断本身是否变化,不判断其指向对象实体内容是否变化

\not_assigned(x,y,...)

表示括号中的变量是否在执行过程中被赋值
被赋值了返回true,反之返回false

\not_modified(x,y,...)

限制括号中变量在方法执行的时候取值不能变化

\nonnullelements(container)

表示该container不能含有null的对象

\type(type)

返回type对应的类型

\typeof(expr)

返回expr对应的准确类型

量化表达式

\forall

表示每个给定范围中的元素都满足对应约束

\exists

表示存在至少一个给定范围中的元素满足对应约束

\sum

返回给定范围中的表达式的和

\product

返回给定范围中的表达式的积

\max

返回给定范围中的表达式的最大值

\min

返回给定范围中的表达式的最小值

\num_of

返回满足相应条件的变量的取值个数

集合表达式

在JML规格中构建一个明确其可以包含什么元素的集合

操作符

E1<:E2

若类型E1为类型E2的子类型(或同类型),返回true,反之返回false

b_expr1<===>b_expr2

b_expr1<=!=>b_expr2

b_expr1与b_expr2均为布尔表达式
表示b_expr1 == b_expr2
或者b_expr1 != b_expr2
但他们的优先级低于==以及!=

b_expr1==>b_expr2

当且仅当b_expr1为true且b_expr2为false时,返回false

\nothing

空集

\everything

全集

方法规格

requires

前置条件,限制方法输入的参数

ensures

后置条件,限制方法产生的结果

side-effects

assignable  可赋值
modifiable  可修改

signals

signals(Exception e) b_expr: b_expr为true,抛出异常e
signals_only:只要满足前置条件就抛出对应的异常

类型规格

invariant

invariatn P:在所有可见状态下都必须满足特性P
constraint:对前序可见状态、当前可见状态关系进行的约束

有用的工具

VScode:高亮显示JML注释(好看)
JUnit:单元测试

利用JML规格准备测试数据

本次测试主要采用黑盒测试,在数据生成器中通过控制不同的指令的数量,尽量覆盖了规格提及的情况,并且通过多次重复测试某一个指令,保证了程序不会超时。

评测机的一些代码:

    from random import choice, randint, random
    instrs = ['ap', 'ap', 'ar', 'ar', 'qv', 'qci',
          'qbs', 'qps', 'ag', 'atg', 'dfg', 'qgps', 'qgvs', 'qgav',
          'am', 'sm', 'qsv', 'qrm', 'qlc', 'qlc', 'qlc', 'qlc', 'qlc',
          'arem', 'anm', 'cn', 'aem', 'sei', 'qp', 'dce', 'qm', 'sim']
    ...
    instrlist += "ap {} {} {} {}\n".format(i, choice(adj) + "_" + choice(name), randint(1000000, 10000000),randint(1, 80))
    relations = randint(people_num, people_num * (people_num) / 2)
    relations = min(LENGTH - 2 * people_num, relations)
    ...

架构

主要的框架其实都已经被规定了 在这里简单说一下图模型的构建以及维护的策略

主要在Network里面动态维护了一些量:

用HashMap把Personid,MessageId,GroupId,EmojiId都做了一一对应,方便后续操作;

qci和qbs都是用的并查集,比起dfs时间复杂度降低了;采用路径压缩使得深度尽量小;
(不得不说hashmap写还是很舒服的)

public static int find(int id, HashMap<Integer, Integer> father) {
        int dad = father.get(id);
        if (dad == id) {
            return id;
        } else {
            dad = find(dad, father);
            father.put(id, dad);
            return dad;
        }
    }

    public static void union(int i1, int i2, HashMap<Integer, Integer> father,
                      HashMap<Integer, Integer> rank, HashMap<Integer, Boolean> dirty) {
        int id1;
        int id2;
        id1 = find(i1, father);
        id2 = find(i2, father);
        int rank1;
        int rank2;
        rank1 = rank.get(id1);
        rank2 = rank.get(id2);
        if (id1 != id2) {
            if (rank1 < rank2) {
                father.put(id1, id2);
                rank.put(id2, rank1 + rank2);
            } else {
                father.put(id2, id1);
                rank.put(id1, rank1 + rank2);
            }
        } else {
            father.put(id2, id1);
            rank.put(id1, rank1 + rank2);
        }
        dirty.put(find(id1, father), false);
    }
}

qlc用的堆优化的dijkstra算法,使用Priorityqueue进行操作

PriorityQueue<int[]> queue = new PriorityQueue<>(priComparator);
        //[distance,id]
        queue.add(new int[]{0, id1});
        while (!queue.isEmpty()) {
            int[] e = queue.poll();
            if (visited.contains(e[1])) {
                continue;
            }
            visited.add(e[1]);
            distance.put(e[1], e[0]);
            if (e[1] == id2) {
                return e[0];
            }
            ((MyPerson) getPerson(e[1])).getValue().forEach((p, v) -> {
                if ((!distance.containsKey(p.getId())) || distance.get(p.getId()) > e[0] + v) {
                    distance.put(p.getId(), e[0] + v);
                    queue.add(new int[]{e[0] + v, p.getId()});
                }
            });
        }

这里为了动态维护,设置了一个dirty的hashmap,如果有addperson这种改变的操作,就把对应的personid的value设置为true,下一次查询的时候要重置

Network拓展

假设出现了几种不同的Person

Advertiser:持续向外发送产品广告

Producer:产品生产商,通过Advertiser来销售产品

Customer:消费者,会关注广告并选择和自己偏好匹配的产品来购买 -- 所谓购买,就是直接通过Advertiser给相应Producer发一个购买消息

Person:吃瓜群众,不发广告,不买东西,不卖东西

如此Network可以支持市场营销,并能查询某种商品的销售额和销售路径等 请讨论如何对Network扩展,给出相关接口方法,并选择3个核心业务功能的接口方法撰写JML规格(借鉴所总结的JML规格模式)

  1. Advertiser Producer Customer 都继承了类Person
  2. 增加类Product,属性包括id,price
  3. 假设advertiser有一定智商,只对customer打广告,并发送给所有的customer
  4. advertiser有一定数量的products

选择了购买商品,打广告,设置用户偏好撰写了JML规格;

customer通过advertiser购买商品

    /*@ public normal_behavior
      @ requires (\exists int i; 0 <= i && i < people.length; people[i].getId() == CustomerId && people[i] instanceof Customer) &&
        (\exists int i; 0 <= i && i<= people.length; people[i].getId() == AdvertiserId && people[i] instanceof Advertiser) &&
        (\exists int i; 0 <= i && i<= products.length;products[i].getId() == ProductId);
      @ ensures getPerson(CustomerId).money = \old(getPerson(customerId).mondy) - getProduct(ProductId).getPrice();
      @ ensures getPerson(AdvertiserId).getRemaining(ProductId) = \old(\getPerson(AdvertiserId).getRemaining(ProductId)) -1 ;
      @ also
      @ public exceptional_behavior
      @ signals (PersonIdNotFoundException e) !(\exists int i; 0 <= i && i < people.length;
      @          people[i].getId() == id && people[i] instanceof CustomerId);
      @ signals (PersonIdNotFoundException e) !(\exists int i; 0 <= i && i < people.length;
      @          people[i].getId() == id && people[i] instanceof AdvertiserId);
      @ signals (ProductIdNotFoundException e) !(\exists int i; 0 <= i && i < products.length;
      @          products[i].getId() == ProductId);
      @*/
      public void purchase(int AdvertiserId,int CustomerId,int ProductId) throw PersonIdNotFoundException,ProductIdNotFoundException;

advertiser投放广告

    /*@ public normal_behavior
      @ requires (\exists int i; 0 <= i && i < people.length; people[i].getId() == advertiserId && people[i] instanceof Advertiser) &&
        (\exists int i; 0 <= i && i<= messages.length;products[i].getId() == adMsgId && products[i] instanceof adMessage);
      @ assignable messages;
      @ ensures !containsMessage(adMsgId) && messages.length == \old(messages.length) - 1 &&
      @         (\forall int i; 0 <= i && i < \old(messages.length) && \old(messages[i].getId()) != adMsgId;
      @         (\exists int j; 0 <= j && j < messages.length; messages[j].equals(\old(messages[i]))));
      @ ensures (\forall int i; 0<= i && i < people.length; people[i] instanceof Customer ;
                (\forall int j; 0 <= j && j < \old(getPerson(i).getMessages().size());
      @          getPerson(i).getMessages().get(j+1) == \old(getPerson(i).getMessages().get(i)) &&
      @          getPerson(i).getMessages().get(0) == \old(getMessage(adMsgId)) &&
      @          \old(getPerson(i).getMessages().size() == \old(getPerson(i).getMessages().size()) + 1));
      @ also
      @ public exceptional_behavior
      @ signals (PersonIdNotFoundException e) !(\exists int i; 0 <= i && i < people.length;
      @          people[i].getId() == id && people[i] instanceof Advertiser);
      @*/
      public void advertise(int advertiserId,int adMsgId) throw PersonIdNotFoundException;

设置用户偏好

          /*@ public normal_behavior
      @ requires (\exists int i; 0 <= i && i < people.length; people[i].getId() == Personid && people[i] instanceof Customer) &&
        (\exists int i; 0 <= i && i<= products.length;products[i].getId() == ProductId);
      @ assignable getPerson(personId).preferences;
      @ ensures (\forall Product i;\old(getPerson(PersonId).prefer(i));getPerson(PersonId).prefer(i));
      @ ensures getPerson(personId).prefer(ProductId);
      @ also
      @ public exceptional_behavior
      @ signals (PersonIdNotFoundException e) !(\exists int i; 0 <= i && i < people.length;
      @          people[i].getId() == id && people[i] instanceof Customer);
      @ signals (ProductIdNotFoundException e) !(\exists int i; 0 <= i && i < products.length;
      @          products[i].getId() == ProductId);
      @*/
      public void setPreference(int PersonId, int ProductId) throw PersonIdNotFoundException,ProductIdNotFoundException;

心得体会

第三单元相对来说是比较友好的单元,但是对JML的运用还是掌握的不太好,虽然能勉强读懂JML规格要我做什么,也能通过实例等对其有所了解,但是如果让我自己写一个JML规格代码还是一件很痛苦的事情。而且两次实验课上都是压着时间线交的答案,很多时候并不太理解,要频繁查手册。

考虑到时间复杂度,这个单元主要把力气都花在了优化上面,非常担心自己会T,也构造了大量的数据去测试某几个容易卡的指令。但不太清楚这一部分内容跟我们这单元所学有什么关系。

posted @ 2022-06-05 10:56  peaceminuczy  阅读(63)  评论(1编辑  收藏  举报