今天介绍了几个想法:
R:The space of representation values (or rep values for short)consists of the values of the actual implementation entities.(表示值构成的空间:实现者看到和使用的值)
A:The space of abstract values consists of the values that the type is designed to support. ( 抽象值构成的空间:client看到和使用的值)These are a figment of our imaginations.
例如,假设我们选择使用字符串来表示一组字符:
public class CharSet {
private String s;
...
}
然后REP空间R包含字符串,抽象空间A是数学字符集。我们可以以图形的方式显示这两个值空间,从一个REP值到它所代表的抽象值有一个弧线。
关于这幅画有几件事要注意:
1.抽象函数(abstraction function)它将REP值映射到它们所代表的抽象值:
AF:R→A
图中的弧表示抽象函数。AF通常是局部的。
2.REP不变量(rep invariant)将代表价值映射到booleans:
RI:R→boolean
For a rep value r, RI(r) is true if and only if r is mapped(映射) by AF.换句话说,RI告诉我们给定的REP值是否格式良好。或者,你可以把RI想为一个集合:它是定义AF的rep值得子集.
例如,下边的图表显示禁止重复字符的字符集代表,RI(“a”)=真,RI(“ac”)=真,和RI(“ACB”)=真的,但是RI(“AA”)=虚假和RI(“ABBC”)=假。符合REP不变的REP值显示在R空间的绿色部分,并且必须映射到A空间中的抽象值。违反REP不变的REP值显示在红色区域,在A空间中没有等效的抽象值。
REP不变量和抽象函数都应该记录在代码中,就在REP本身的声明旁边:
public class CharSet {
private String s;
// Rep invariant:
// s contains no repeated characters
// Abstraction function:
// AF(s) = {s[i] | 0 <= i < s.length()}
...
}
关于抽象函数和REP不变量的一个常见混淆是,它们是由REP和抽象值空间的选择决定的,甚至是由抽象值空间单独决定的。如果是这样的话,那就没什么用了,因为他们会说一些多余的东西,这些东西已经在其他地方找到了。
抽象值空间本身并不能决定AF或RI:同一抽象类型可以有几个表示。
相同的rep值空间,可以有不同的rep不变量。即使具有相同类型的REP值空间和相同的REP不变量,我们仍然可以用不同的抽象函数(AF)对REP进行不同的解释。
The essential point is that implementing an abstract type means not only choosing the two spaces – the abstract value space for the specification and the rep value space for the implementation – but also deciding which rep values are legal, and how to interpret them as abstract values.(决定哪些代表价值是合法的,以及如何将它们解释为抽象值)
Example: rational numbers(有理数)
Here’s an example of an abstract data type for rational numbers. Look closely at its rep invariant and abstraction function. It would be completely reasonable to design another implementation of this same ADT with a more permissive RI.
public class RatNum {
private final int numerator;
private final int denominator;
// Rep invariant:
// denominator > 0
// numerator/denominator is in reduced form,
// i.e. gcd(|numerator|,denominator) = 1
// Abstraction function:
// AF(numerator, denominator) = numerator/denominator
/** Make a new RatNum == n.
* @param n value
*/
public RatNum(int n) {
numerator = n;
denominator = 1;
checkRep();
}
/** Make a new RatNum == (n / d).
* @param n numerator
* @param d denominator
* @throws ArithmeticException if d == 0 */
public RatNum(int n, int d) throws ArithmeticException {
// reduce ratio to lowest terms
int g = gcd(n, d);
n = n / g;
d = d / g;
// make denominator positive
if (d < 0) {
numerator = -n;
denominator = -d;
} else {
numerator = n;
denominator = d;
}
checkRep();
}
}
如果您的实现在运行时断言REP不变,那么您可以尽早捕获bug。这里有一个方法RatNum测试它的REP不变量:
// Check that the rep invariant is true
// *** Warning: this does nothing unless you turn on assertion checking
// by running Java with -enableassertions (or -ea)
private void checkRep() {
assert denominator > 0;
assert gcd(Math.abs(numerator), denominator) == 1;
}
你当然应该调用checkRep()在创建或转换REP的每个操作的末尾断言REP不变量-换句话说,创建者(creators)、生产者(producers)和变异器(mutators)。回顾RatNum上面的代码,将看到它在两个构造函数的末尾调用checkRep()。
Observer methods通常不需要调用checkRep(),但这样做是很好的防守练习。为什么?呼叫checkRep()在每种方法中,包括观察者,意味着我们将更有可能捕获REP暴露导致的REP不变违规行为。
通过对抽象空间A和REP空间R的新理解,我们可以定义:抽象值不应该改变。但是,只要rep值继续映射到同一个抽象值,实现就可以自由地对其进行变异,这样客户机就看不到更改。这种变化叫做有益突变.
下面是一个使用替代代表的简单示例RatNum我们之前看到的类型。该REP具有较弱的REP不变量,不需要以最低的术语存储分子和分母:
public class RatNum {
private int numerator;
private int denominator;
// Rep invariant:
// denominator != 0
// Abstraction function:
// AF(numerator, denominator) = numerator/denominator
/**
* Make a new RatNum == (n / d).
* @param n numerator
* @param d denominator
* @throws ArithmeticException if d == 0
*/
public RatNum(int n, int d) throws ArithmeticException {
if (d == 0) throw new ArithmeticException();
numerator = n;
denominator = d;
checkRep();
}
...
}
这个较弱的REP不变量允许RatNum算术运算简单地省略,将结果降到最低项。但是当向人类展示结果的时候,我们首先简化它:
/**
* @return a string representation of this rational number
*/
@Override
public String toString() {
int g = gcd(numerator, denominator);
numerator /= g;
denominator /= g;
if (denominator < 0) {
numerator = -numerator;
denominator = -denominator;
}
checkRep();
return (denominator > 1) ? (numerator + "/" + denominator) : (numerator + "");
}
注意这个toString实现重新分配私有字段numerator和denominator,对表示进行变异–即使它是不可变类型上的观察者方法!但是,关键的是,突变并没有改变抽象的价值。将分子和分母除以相同的公共因子,或将两者乘以-1,对抽象函数的结果没有影响,AF(numerator, denominator) = numerator/denominator。另一种思考它的方法是AF是一个多对一的函数,并且REP值已经改变为另一个仍然映射到相同抽象值的函数。所以突变是无害的,或者有益的.
在类中记录抽象函数和REP不变量是很好的做法,在声明REP的私有文件时使用注释。当描述REP不变和抽象函数时,必须精确:
这里有一个例子Tweet它的REP不变、抽象功能和REP暴露的安全性都有完整的文档记录:
// Immutable type representing a tweet.
public class Tweet {
private final String author;
private final String text;
private final Date timestamp;
// Rep invariant:
// author is a Twitter username (a nonempty string of letters, digits, underscores)
// text.length <= 280
// Abstraction function:
// AF(author, text, timestamp) = a tweet posted by author, with content text,
// at time timestamp
// Safety from rep exposure:
// All fields are private;
// author and text are Strings, so are guaranteed immutable;
// timestamp is a mutable Date, so Tweet() constructor and getTimestamp()
// make defensive copies to avoid sharing the rep's Date object with clients.
// Operations (specs and method bodies omitted to save space)
public Tweet(String author, String text, Date timestamp) { ... }
public String getAuthor() { ... }
public String getText() { ... }
public Date getTimestamp() { ... }
}
注意,我们没有任何显式的REP不变条件timestamp(除了传统的假设timestamp!=null,我们对所有对象引用都有)。但我们仍然需要包括timestamp在REP公开安全性参数中,因为整个类型的不变性属性依赖于所有字段保持不变。
以下是支持RatNum.
// Immutable type representing a rational number.
public class RatNum {
private final int numerator;
private final int denominator;
// Rep invariant:
// denominator > 0
// numerator/denominator is in reduced form, i.e. gcd(|numerator|,denominator) = 1
// Abstraction function:
// AF(numerator, denominator) = numerator/denominator
// Safety from rep exposure:
// All fields are private, and all types in the rep are immutable.
// Operations (specs and method bodies omitted to save space)
public RatNum(int n) { ... }
public RatNum(int n, int d) throws ArithmeticException { ... }
...
}
请注意,不可变的REP特别容易为REP暴露的安全性辩护。