Дизайн API для идиотостойкой итерации без обобщений - PullRequest
3 голосов
/ 15 июня 2011

Когда вы разрабатываете API для библиотеки кодов, вы хотите, чтобы его было легко использовать хорошо, а трудно - плохо. В идеале вы хотите, чтобы это было доказательство идиота.

Возможно, вы также захотите сделать его совместимым со старыми системами, которые не могут обрабатывать дженерики, такими как .Net 1.1 и Java 1.4. Но вы не хотите, чтобы из-за более нового кода это было болезненно.

Мне интересно, как лучше сделать вещи легко повторяемыми в безопасном для типов виде ... Вспоминая, что вы не можете использовать дженерики, так что Java Iterable<T> отсутствует, как и .Net * IEnumerable<T>.

Вы хотите, чтобы люди могли использовать расширенный цикл for в Java (for Item i : items) и цикл foreach / For Each в .Net, и вы не хотите, чтобы им приходилось выполнять какое-либо приведение. По сути, вы хотите, чтобы ваш API был теперь дружественным, а также обратно совместимым.

Лучший типобезопасный вариант, который я могу придумать, это массивы. Они полностью совместимы с предыдущими версиями и , их легко перебирать типизированным способом. Но массивы не идеальны, потому что вы не можете сделать их неизменными. Поэтому, когда у вас есть неизменяемый объект, содержащий массив, который вы хотите, чтобы люди могли перебирать, для поддержания неизменности вы должны предоставлять защитную копию при каждом обращении к нему.

В Java выполнение (MyObject[]) myInternalArray.clone(); выполняется очень быстро. Я уверен, что аналог в .Net тоже очень быстрый. Если у вас есть как:

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(), и это, вероятно, уменьшит вероятность того, что кто-то будет использовать его неправильно. Но это также затруднит поиск людей, когда они просто захотят перебрать встречи.

Вы бы, конечно, документ appointments() четко заявили, что он возвращает защитную копию. Но многие люди не будут читать эту конкретную часть документации.

Хотя я бы приветствовал предложения, мне кажется, что не существует идеального способа сделать его простым, понятным, безопасным для типов, и идиотом. Я потерпел неудачу, если меньшинство людей невольно клонировало массивы тысяч раз, или это приемлемая цена за простую, безопасную для типов итерацию для большинства ?


NB. Мне случается, что я проектирую эту библиотеку для Java и .Net, поэтому я попытался сделать этот вопрос применимым к обоим. И я пометил это как независимый от языка, потому что это проблема, которая может возникнуть и для других языков. Примеры кода на Java, но C # будет аналогичным (хотя и с возможностью сделать свойство доступа Appointments).


ОБНОВЛЕНИЕ : Я провел несколько быстрых тестов производительности, чтобы увидеть, насколько это изменилось в Java. Я проверял:

  1. клонирование массива один раз и итерация по нему с использованием расширенного цикла for
  2. перебор ArrayList с использованием расширенный цикл
  3. перебирает неизменяемое ArrayList (от Collections.unmodifyableList) используя расширенный цикл
  4. итерация по массиву плохим способом (повторное клонирование при проверке длины и при получении каждого проиндексированного элемента).

Для 10 объектов относительные скорости (многократные повторения и медиана) были такими:

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

Для 100 объектов:

  1. 1300
  2. 4900
  3. 6,300
  4. 85500

Для 1000 объектов:

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

Для 10000 объектов:

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

Грубые цифры, конечно, но достаточно, чтобы убедить меня в двух вещах:

  • Клонирование, затем итерация определенно не проблема производительности. по факту это постоянно быстрее, чем с помощью Список. (это почему Java Метод enum.values ​​() возвращает защитная копия массива вместо неизменный список .)
  • Если вы неоднократно вызываете метод, многократно клонировать массив без необходимости, производительность становится все более и более важной, чем больше рассматриваемые массивы. Это довольно ужасно. Там нет сюрпризов.

Ответы [ 3 ]

6 голосов
/ 15 июня 2011

clone () быстрый, но не тот, который я бы назвал супер быстрым.

Если вы не доверяете людям эффективно писать циклы, я бы не позволил им написать цикл (что также устраняет необходимость в клоне ())

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

class Schedule {
    public void forEachAppointment(AppointmentHandler ah) {
        for(Appointment a: internalArray)
            ah.onAppointment(a);
    }
}
1 голос
/ 15 июня 2011

Что касается отказа меньшинства пользователей, те, кто будет вызывать один и тот же метод для получения одного и того же объекта на каждой итерации цикла, будут требовать неэффективности независимо от дизайна API.Я думаю, что, если это хорошо задокументировано, не так уж много нужно просить пользователей подчиняться некоторому подобию здравого смысла.

1 голос
/ 15 июня 2011

Поскольку у вас не может быть и того и другого, я бы посоветовал вам создать предварительную и универсальную версию вашего API. В идеале базовая реализация может быть в основном одинаковой, но на самом деле, если вы хотите, чтобы ее было легко использовать для тех, кто использует Java 1.5 или более позднюю версию, они ожидают использования Generics и Iterable, а также всех новых возможностей языка.

Я думаю, что использование массивов не должно существовать. В любом случае это не упрощает использование API.

ПРИМЕЧАНИЕ: я никогда не использовал C #, но я ожидаю, что то же самое верно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...