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

时间:2011-06-15 15:49:54

标签: java .net api language-agnostic api-design

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

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

我想知道以一种类型安全的方式使事物易于迭代的最佳方法...记住你不能使用泛型,所以Java的Iterable<T>就出来了,就像.Net的{{1 }}

您希望人们能够在Java IEnumerable<T>中使用增强型for循环,在.Net中使用(for Item i : items) / foreach循环,并且您不希望他们拥有做任何演员。基本上,您希望您的API现在既友好又向后兼容。

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

在Java中,For Each的速度非常快。我确信.Net中的等价物也非常快。如果你喜欢:

(MyObject[]) myInternalArray.clone();
人们可以这样做:

class Schedule {
   private Appointment[] internalArray;
   public Appointment[] appointments() {
       return (Appointment[]) internalArray.clone();
   }
}

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

但他们可以做类似的事情:

for (Appointment a : schedule.appointments()) {
    a.doSomething();
}

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

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

您可以调用方法for (int i = 0; i < schedule.appointments().length; i++) { Appointment a = schedule.appointments()[i]; } 而不是toAppointmentArray(),这可能会降低任何人以错误的方式使用它的可能性。但它也会让人们更难以找到他们只想迭代约会的时候。

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

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


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


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

  1. 克隆数组一次,然后使用增强型for循环迭代它
  2. 使用遍历ArrayList 增强的for循环
  3. 迭代不可修改的 ArrayList(来自 Collections.unmodifyableList)使用 增强的for循环
  4. 以不好的方式遍历数组(在长度检查中重复克隆它) 并获取每个索引项目时。)
  5. 对于10个物体,相对速度(多次重复并取中位数)如下:

    1. 1000
    2. 1300
    3. 1300
    4. 5000
    5. 对于100个对象:

      1. 1300
      2. 4900
      3. 6300
      4. 85500
      5. 对于1000个对象:

        1. 6400
        2. 51700
        3. 56200
        4. 7000300
        5. 对于10000个对象:

          1. 68000
          2. 445000
          3. 651000
          4. 655180000
          5. 粗略的数字肯定,但足以说服我两件事:

3 个答案:

答案 0 :(得分:6)

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

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

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

class Schedule {
    public void forEachAppointment(AppointmentHandler ah) {
        for(Appointment a: internalArray)
            ah.onAppointment(a);
    }
}

答案 1 :(得分:1)

由于您无法实现两种方式,我建议您创建API的预泛化版和泛型版。理想情况下,底层实现可能大致相同,但事实是,如果您希望它对于使用Java 1.5或更高版本的任何人来说都很容易使用,他们会期望使用Generics和Iterable以及所有新的languange功能。 / p>

我认为数组的使用应该是不存在的。在任何一种情况下,它都不是一个易于使用的API。

注意:我从未使用过C#,但我希望同样如此。

答案 2 :(得分:1)

就少数用户失败而言,无论API设计如何,那些在循环的每次迭代中调用相同方法获取相同对象的用户都会要求效率低下。我认为,只要记录得很好,要求用户遵守一些常识并不是太多。