论如何从零开始攻略一个ADT——AF RI具体设计篇
关于AF、RI的定义,在此不赘述。但由于定义过于泛泛,导致在实现的时候,不知道该写什么。
下面将通过JDK中的实例,来分析AF、RI具体该写什么。
本文仅为个人观点,各位当个乐子看就好:)
实例来源于JDK的接口List及其实现ArrayList。
例子用的是某实验的ConcreteEdgesGraph的实现。
下面的介绍可能部分超出AF、RI的要求。
1.AF的实现:
下面按顺序给出List以及ArrayList中的AF及其分析。
用小体字表示在实验中AF实现的实例。
关于ADT的大体概述,大多数情况下这个在题目或者需求里都有,可以直接抄上去。
表示一个有向无环图,其中每一条边均带有边权,每一个点都有一个label来指明。
/** * Lists (like Java arrays) are zero based. */
关于ADT初始情况的介绍,在新建一个ADT之后,里面有没有元素之类的。
初始时,图中的点集和边集均为空集。
/** * The user of this interface has precise control over where * in the list each element is inserted. * The user can access elements by their integer index (position in * the list), and search for elements in the list. */
关于能对ADT中方法的大体介绍,主要是observor或者mutator。
实际上,需要实现的操作(方法)在要求里一般也会有,可以直接抄上去。
对于一张图,可以通过给定边的起点、终点、边权,来在图中插入、删除边。
同时可以查询图中所有点,以及根据给定点的label来查询该点的前驱、后继。
/** * Unlike sets, lists typically allow duplicate elements. */
与同一接口的不同实现之间的比较,虽然是在AF里,但这里主要是指RI上的不同,具体来说是哪些表示空间里的元素对应到不同的哪些抽象空间中的元素。
至于方法上的不同,这个会写在方法的spec里,所以不需要关注。
不同于以点实现的图,以边实现的图不需要额外的内部辅助方法,从而有更高的可扩展和可维护性。
/** * The List interface places additional stipulations, beyond those * specified in the Collection interface. */
对超出接口中spec的实现的说明,还是主要是RI上的不同,例如哪些输入对接口来说合法,但对这个implemention不合法。
而额外实现的方法,如果不能通过接口调用的话,应该只能在test里用了吧,这样提不提其实也无所谓了。
相较于Graph接口,扩展了toString方法,其输出格式为……
除此之外,其他的方法的spec均与Graph一致。
2.RI的实现:
RI大体分为两部分:一是各个field的意义,是写在定义field处;二是对于各种边界情况、特殊情况的处理,是写在开头的spec中。
各个field的意义没什么好说的,基本按照“(变量名)是一个(数据类型)的(ADT),用来记录(抽象空间中的对象)”来写就好。
public class ConcreteEdgesGraph<L> implements Graph<L> { private final Set<L> vertices = new HashSet<>(); private final List<Edge<L>> edges = new ArrayList<>(); }
vertices是一个L类型的set,用来记录图中的所有点。
edges是一个Edge<L>类型的list,用来记录图中所有的边。
特殊情况的处理就很麻烦,因为客户的输入五花八门,所以很难考虑到所有情况。
Link的spec的最后,泛泛地列出了几种情况:
/** * While it is permissible for lists to contain themselves as elements, * extreme caution is advised: the equals and hashCode * methods are no longer well defined on such a list. * Some list implementations have restrictions on the elements that * they may contain. * For example, some implementations prohibit null elements, * and some have restrictions on the types of their elements. */
关于能够放入ADT的数据类型和数据值的限制,以及如果放入某些不合法的数据类型,会导致哪些方法失效。
这些都比较基本,所有ADT的RI都需要写。
然后是一些个性化的RI,对ADT本身一些性质的分析。
我个人感觉,这些个性化的RI与其说是为了维护表示不变性,更像是用来检查下面各个方法的实现是否合法的一种手段。
上述RI都会用checkRep来保证。
每个插入点的label均不能为null → label == null?
不允许有自环 → 每个点的后继里是否有它本身?
不允许有重边,新插入的重边会被用来更新 → 边的总数是否小于完全图的边数?
3.避免表示泄露:
注意三个关键词:private、final、immutable,所有的操作均为了赋予field这三个属性。
首先,所有观察ADT属性的操作全都用observor来实现,这没什么好说的。
其次,由于现在写的ADT都较为简单,private和final这两个前缀能加就加上。
immutabl的field就比较棘手,由于是被数据类型本身决定了,所以需要多提一句:会使用防御式拷贝来保护。
// Safety from rep exposure: // All fields are private and final // vertices and edges are mutable // so defensive copies should be used