软件构造–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等的使用,在后续编写中可以加以运用。