没有泛型的Idiot-Proof迭代的API设计

MB.*_*MB. 3 .net java language-agnostic api api-design

当您为代码库设计API时,您希望它易于使用,并且难以使用.理想情况下,你希望它是白痴证明.

您可能还希望使其与无法处理泛型的旧系统兼容,例如.Net 1.1和Java 1.4.但是你不希望从较新的代码中使用它会很痛苦.

我想知道以一种类型安全的方式使事物易于迭代的最佳方法......记住你不能使用泛型,因此Java Iterable<T>就是这样的,就像.Net一样IEnumerable<T>.

您希望人们能够在Java中使用增强的for循环,在.Net中(for Item i : items)使用foreach/ For Eachloop,并且您不希望他们必须进行任何转换.基本上,您希望您的API现在既友好又向后兼容.

我能想到的最好的类型安全选项是数组.它们完全向后兼容,并且易于以类型安全的方式进行迭代.但是数组并不理想,因为你不能让它们变成不可变的.因此,当您拥有一个包含数组的不可变对象时,您希望人们能够迭代,为了保持不变性,您必须在每次访问它时提供防御性副本.

在Java中,做得(MyObject[]) myInternalArray.clone();非常快.我确信.Net中的等价物也非常快.如果你喜欢:

class Schedule {
   private Appointment[] internalArray;
   public Appointment[] appointments() {
       return (Appointment[]) internalArray.clone();
   }
}
Run Code Online (Sandbox Code Playgroud)

人们可以这样做:

for (Appointment a : schedule.appointments()) {
    a.doSomething();
}
Run Code Online (Sandbox Code Playgroud)

它将简单,清晰,类型安全,快速.

但他们可以这样做:

for (int i = 0; i < schedule.appointments().length; i++) {
    Appointment a = schedule.appointments()[i];
}
Run Code Online (Sandbox Code Playgroud)

然后它会非常低效,因为整个约会数组将在每次迭代时克隆两次(一次用于长度测试,一次用于获取索引处的对象).如果阵列很小,这不是一个问题,但如果阵列中有数千个项目,则非常可怕.育.

有人会这样做吗?我不确定......我猜这在很大程度上是我的问题.

您可以调用该方法toAppointmentArray()而不是appointments(),这可能会使任何人以错误的方式使用它的可能性降低.但它也会让人们更难以找到他们只想迭代约会的时候.

当然,你会appointments()清楚地记录下来,说它会返回一个防御性的副本.但很多人不会阅读那些特定的文档.

虽然我欢迎提出建议,但在我看来,没有完美的方法可以使它变得简单,清晰,类型安全,而且是白痴证明.如果少数人不知道克隆阵列数千次,或者对于大多数人来说,为简单的,类型安全的迭代付出的代价是否可以接受?


NB我碰巧正在为Java和.Net设计这个库,这就是为什么我试图让这个问题适用于两者.我将其标记为与语言无关,因为这也是其他语言可能出现的问题.代码示例使用Java,但C#类似(尽管可以选择使Appointments访问者成为属性).


更新:我做了一些快速性能测试,看看它在Java中有多大差异.我测试过:

  1. 克隆数组一次,并使用增强的for循环迭代它
  2. 使用增强的for循环遍历ArrayList
  3. 使用增强的for循环遍历不可修改的ArrayList(来自Collections.unmodifyableList)
  4. 以错误的方式遍历数组(在长度检查中以及获取每个索引项时重复克隆它).

对于10个对象,相对速度(多次重复并取中位数)如下:

  1. 1000
  2. 1300
  3. 1300
  4. 5000

对于100个对象:

  1. 1300
  2. 4900
  3. 6300
  4. 85,500

对于1000个对象:

  1. 6400
  2. 51700
  3. 56200
  4. 7000300

对于10000个对象:

  1. 68000
  2. 445000
  3. 651000
  4. 655180000

粗略的数字肯定,但足以说服我两件事:

Pet*_*rey 6

clone()很快但不是我所说的超级快.

如果你不相信人们有效地编写循环,我不会让他们写一个循环(这也避免了对clone()的需要)

interface AppointmentHandler {
    public void onAppointment(Appointment appointment);
}

class Schedule {
    public void forEachAppointment(AppointmentHandler ah) {
        for(Appointment a: internalArray)
            ah.onAppointment(a);
    }
}
Run Code Online (Sandbox Code Playgroud)