MIT6.031学习笔记:AF&RI

Reading 11: Abstraction Functions & Rep Invariants

目标

今天介绍了几个想法:

  • 抽象函数(abstraction functions)
  • 表示不变量(representation invariants)

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值到它所代表的抽象值有一个弧线。
MIT6.031学习笔记:AF&RI_第1张图片
关于这幅画有几件事要注意:

  • 每个抽象值都由某个REP值映射到。(满射) 实现抽象类型的目的是支持对抽象值的操作。那么,我们大概需要能够创建和操作所有可能的抽象值,因此它们必须是可表示的。
  • 一些抽象值由多个REP值映射到。(未必单射)之所以会出现这种情况,是因为表示不是一个严格的编码。将无序字符集表示为字符串的方法不止一种。
  • 并不是所有的REP值都被映射。(未必双射)在这种情况下,字符串“ABBC”没有映射,因为我们已经决定REP字符串不应该包含重复的字符串。这将使我们能够终止remove方法,因为我们知道最多只能有一个实例。

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空间中没有等效的抽象值。
MIT6.031学习笔记:AF&RI_第2张图片
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();    
        }
  } 

Checking the rep invariant(检查REP不变量)

如果您的实现在运行时断言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不变违规行为。

Beneficent mutation(有益突变)

通过对抽象空间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值已经改变为另一个仍然映射到相同抽象值的函数。所以突变是无害的,或者有益的.

Documenting the AF, RI, and safety from rep exposure

在类中记录抽象函数和REP不变量是很好的做法,在声明REP的私有文件时使用注释。当描述REP不变和抽象函数时,必须精确:

  • RI仅仅是“所有字段都是有效的”这样的泛型语句是不够的。*REP不变量的工作是精确地解释什么使字段值有效或无效。
  • AF提供“表示一组字符”这样的通用解释是不够的。**抽象函数的任务是精确定义具体字段值的解释方式。**作为功能,如果我们使用文档化的AF并在实际(合法)字段值中替换,我们应该获得它们所代表的单个抽象值的完整描述。
  • REP暴露安全论点。这是一个注释,检查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暴露的安全性辩护。

你可能感兴趣的:(MIT6.031学习笔记:AF&RI)