软件构造--Lab2小结

软件构造–Lab2小结

目前距离Lab2结束已经有一段时间,下面重新简单回顾一下Lab2中出现的问题,进行简单总结。

接口Interface的静态方法实现

在早期,接口Interface中是不能有方法实现的,interface可以看做是最抽象的类,其内部的方法需要在后续实现类中实现。一个类可以实现多个接口从而继承多个接口的方法、属性等,但不能有多个父类。
但是,在JDK8之后,可以在interface中有static方法实现。在Lab2中有相应的体现,以下是Lab2中的接口graph:

/* Copyright (c) 2015-2016 MIT 6.005 course staff, all rights reserved.
 * Redistribution of original or derived work requires permission of course staff.
 */
package P1.graph;

import java.util.Map;
import java.util.Set;

/**
 * A mutable weighted directed graph with labeled vertices.
 * Vertices have distinct labels of an immutable type {@code L} when compared
 * using the {@link Object#equals(Object) equals} method.
 * Edges are directed and have a positive weight of type {@code int}.
 * 
 * <p>PS2 instructions: this is a required ADT interface.
 * You MUST NOT change the specifications or add additional methods.
 * 
 * @param <L> type of vertex labels in this graph, must be immutable
 */
public interface Graph<L> {
    
    /**
     * Create an empty graph.
     * 
     * @param <L> type of vertex labels in the graph, must be immutable
     * @return a new empty weighted directed graph
     */
    public static <L> Graph<L> empty() {
        return new ConcreteEdgesGraph<>();
    }
    
    /**
     * Add a vertex to this graph.
     * 
     * @param vertex label for the new vertex
     * @return true if this graph did not already include a vertex with the
     *         given label; otherwise false (and this graph is not modified)
     */
    public boolean add(L vertex);
    
    /**
     * Add, change, or remove a weighted directed edge in this graph.
     * If weight is nonzero, add an edge or update the weight of that edge;
     * vertices with the given labels are added to the graph if they do not
     * already exist.
     * If weight is zero, remove the edge if it exists (the graph is not
     * otherwise modified).
     * 
     * @param source label of the source vertex
     * @param target label of the target vertex
     * @param weight nonnegative weight of the edge
     * @return the previous weight of the edge, or zero if there was no such
     *         edge
     */
    public int set(L source, L target, int weight);
    
    /**
     * Remove a vertex from this graph; any edges to or from the vertex are
     * also removed.
     * 
     * @param vertex label of the vertex to remove
     * @return true if this graph included a vertex with the given label;
     *         otherwise false (and this graph is not modified)
     */
    public boolean remove(L vertex);
    
    /**
     * Get all the vertices in this graph.
     * 
     * @return the set of labels of vertices in this graph
     */
    public Set<L> vertices();
    
    /**
     * Get the source vertices with directed edges to a target vertex and the
     * weights of those edges.
     * 
     * @param target a label
     * @return a map where the key set is the set of labels of vertices such
     *         that this graph includes an edge from that vertex to target, and
     *         the value for each key is the (nonzero) weight of the edge from
     *         the key to target
     */
    public Map<L, Integer> sources(L target);
    
    /**
     * Get the target vertices with directed edges from a source vertex and the
     * weights of those edges.
     * 
     * @param source a label
     * @return a map where the key set is the set of labels of vertices such
     *         that this graph includes an edge from source to that vertex, and
     *         the value for each key is the (nonzero) weight of the edge from
     *         source to the key
     */
    public Map<L, Integer> targets(L source);
    
}

其中public static Graph empty()即为接口中静态方法,可以有实现。通过接口中此静态方法,封装了继承接口的类的构造方法,起到一定的封装效果,可以在后续的实验、编写中使用。

ADT简化代码的作用

在Lab2中,P1中实现基于点Vertex和基于边Edge的图的ADT,其中有些许图的基本操作的方法,如add、remove等,基于这些ADT,在P2实现的Friendship类更加简洁(相比与Lab1),具体体现在Friendship类中各个方法实现以ADT实现的方法为基础,不用再重写操作。例如,下图分别是Lab1、Lab2中的Friendship类的某个方法的实现(同一个任务):
首先是方法规约:

/**
     * getDistance get Distance of two of them
     *
     * @param p1 path starting person
     * @param p2 path ending person
     * @return distance between 2 persons or -1 when unlinked or 0 when p1 equals p2
     */

其次是Lab1的实现:

public int getDistance(Person a, Person b) {//DFS深搜
        if (a == b) {
            if (!IsExit(a)) {//检查人员a,人员b是否在社交网络中
                System.out.println("人员" + a.getName() + "不在社交网络中");
                return -2;
            } else {
                return 0;//是同一个人且在社交网络总,距离为0
            }
        } else {
            if (!IsExit(a)) {//检查人员a,人员b是否在社交网络中
                System.out.println("人员" + a.getName() + "不在社交网络中");
                return -2;
            } else if (!IsExit(b)) {
                System.out.println("人员" + b.getName() + "不在社交网络中");
                return -2;
            }
            Queue<Person> queue = new LinkedList<>();//DFS深搜,找最短路径
            Map<Person, Integer> DFS = new HashMap<>();
            DFS.put(a, 0);
            queue.offer(a);
            while (!queue.isEmpty()) {
                Person head = queue.poll();
                List<Person> friends = head.getFriends();
                for (Person key : friends) {
                    if (!DFS.containsKey(key)) {
                        queue.offer(key);
                        DFS.put(key, DFS.get(head) + 1);
                        if (key == b) {
                            return DFS.get(key);
                        }
                    }
                }
            }
            return -1;//二者不连通,距离为-1
        }
    }

然后是Lab2的实现:

 public int getDistance(Person p1, Person p2) {
        if (p1.equals(p2)) {//两个人相等则认为距离为0
            return 0;
        }
        Map<Person, Integer> distance = new HashMap<>();//存储graph中人之间的社交距离
        List<Person> visited = new ArrayList<>();
        Queue<Person> queue = new LinkedList<>();
        Set<Person> persons = graph.vertices();
        for (Person person : persons) {//搜索图前初始化
            distance.put(person, 0);
        }
        visited.add(p1);
        for (queue.offer(p1); !queue.isEmpty(); ) {
            Person person = queue.poll();
            for (Map.Entry<Person, Integer> edge : graph.targets(person).entrySet()) {
                Person target = edge.getKey();
                if (!visited.contains(target)) {
                    queue.offer(target);
                    visited.remove(target);//更新当前所在
                    visited.add(target);
                    distance.remove(target);//更新当前距离
                    distance.put(target, distance.get(person) + 1);
                    if (target.equals(p2))//通过无权图搜索找到目标人物
                        return distance.get(target);
                }
            }
        }
        return -1;
    }

可以看到Lab1中的getDistance方法的检查在内部实现,而Lab2的检查则交给了ADT的set方法实现。
通过ADT的抽象和类的继承机制,能够实现复用,使代码更加简洁,避免通过大量的分支结构来写很多重复的代码。利用子类型的多态性增加了代码的可复用性和可维护性。

规约的重要性

在Lab1中对规约基本没有考察,而Lab2对规约有一定的要求。在Lab2中,按照的是测试优先的编写模式,即测试在实现之前进行设计,那么测试能够依靠的只有规约。
规约用于对方法、类等进行描述,告知客户端如何正确地使用该方法、该类。测试优先的编写模式属于黑盒测试,根据规约对方法、类等的描述进行设计测试用例,此时规约就显得尤为重要,要在开始阶段就设计好,其正确性与后续的测试、方法实现直接挂钩,因此要在设计规约的时候尽可能考虑周全,在后续实现中尽量不要改动规约,否则可能造成需要大面积改动的情况,有极大的开销。
在Lab2中,规约大部分给定了,只有小部分的暂未给定,上述的情况还算好,当遇到需要自行大规模设计规约时候(如后续的Lab3),就要考虑到上述的情况了。

Junit的@Before和@After

在使用Junit进行测试的时候,设计用例常常需要进行重复的构造实例的过程,传统的Ctrl+C/V虽然可以一定程度上解决此问题,但是还是没有太大的技术含量,可以利用Junit的@Before和@After此两种注解(注意不是注释,常见的注解还有@Override,还有Junit提供的@Test–告诉IDE此部分是测试方法)
@Before注解的方法在Junit执行测试前都会执行一次,例如可以将初始的构造实例的代码放入其中;
@After注解的方法在Junit执行测试后都会执行一次,例如如果有打开文件等操作,可以在后面提供关闭等操作。

综上,Lab2中需要注意interface中的静态方法实现、ADT作用、测试优先编写模式、规约重要性以及@Before和@After等的使用,在后续编写中可以加以运用。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

深海质粒ABCC9

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值