Как дела? Предлог.

Мой друг из Университета Рутгерса всегда отвечал на вопрос "Что случилось?" с последовательным ответом: «Предлог». Я слишком много раз попадал в эту ловушку.

Вы когда-нибудь задумывались о том, как часто мы используем предлоги в наших API Java?

В методах Коллекций Eclipse мы используем несколько разных предлогов. Каждый из них имеет разное значение. Некоторые предлоги, которые появляются в Коллекциях Eclipse несколько раз: with, of, by, as, to, from, into. Когда мы используем предлог в имени метода, он должен помочь ясно передать смысл. Если этого не произойдет, то нам было бы лучше без этого.

Входят два предлога. Уходит один предлог.

В этом году на JavaOne я описал битву, которая когда-то была между двумя предлогами для именования методов нашей фабрики коллекций в API Eclipse Collections. Битва была между of и with.

MutableList<String> list = Lists.mutable.of("1", "2", "3");
                           vs.
MutableList<String> list = Lists.mutable.with("1", "2", "3");

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

Предлоги of и with хорошо подходят для именования фабричных методов создания коллекций. Лично я предпочитаю with, в основном потому, что это то, что использовалось с Smalltalk. В Smalltalk я бы регулярно писал следующее:

|set|
set := Set with: ‘1’ with: ‘2’ with: ‘3’.

Ниже приведен эквивалент использования Java с коллекциями Eclipse.

MutableSet<String> set = Sets.mutable.with("1", "2", "3");

При желании вы также можете создать коллекцию, используя фабричный метод of.

MutableSet<String> set = Sets.mutable.of("1", "2", "3");

Есть также формы, которые принимают Iterable в качестве параметра. Они называются ofAll и withAll.

В java.util.Collection есть методы для добавления и удаления элементов в коллекции и из них. Они названы add, addAll, remove и removeAll. Эти четыре метода возвращают boolean. Это делает их непригодными для быстрого написания кода.

У нас есть собственные изменяемые интерфейсы в коллекциях Eclipse, поэтому мы знали, что можем исправить проблему беглости, используя один из двух предлогов. Мы решили использовать with, потому что у with есть естественная противоположность without.

Set<String> set = 
    Sets.mutable.with("1", "2", "3")
        .with("4")
        .without("2");
Assert.assertEquals(Sets.mutable.with("1", "3", "4"), set);

Этот шаблон именования также хорошо работал при добавлении элементов через Iterable.

Set<String> set =
        Sets.mutable.with("1", "2", "3")
                .withAll(Lists.mutable.with("4"))
                .withoutAll(Lists.mutable.with("1", "3"));
Assert.assertEquals(Sets.mutable.with("2", "4"), set);

Как видите, у нас есть with, withAll, without и withoutAll как методы экземпляра непосредственно в наших изменяемых коллекциях. Вместо того, чтобы возвращать логическое значение, такое как add или remove, эти методы возвращают this, который представляет собой коллекцию, с которой работает метод. Эти методы имеют хорошую симметрию с существующими методами Collection, которые возвращают boolean, а также друг с другом.

Мы также распространили этот шаблон на наши неизменяемые коллекции.

ImmutableSet<String> set =
        Sets.immutable.with("1", "2", "3")
                .newWithAll(Lists.mutable.with("4"))
                .newWithoutAll(Lists.mutable.with("1", "3"));
Assert.assertEquals(Sets.mutable.with("2", "4"), set);

В изменяемом случае методы withAll и withoutAll изменяют существующую коллекцию. В случаях newWithAll и newWithoutAll каждый раз возвращается новая коллекция, таким образом сохраняя неизменность исходной коллекции.

Атака клонов

Предлог of проиграл битву методов фабрики коллекций на основе экземпляров в Eclipse Collections, потому что для of нет хорошей естественной противоположности, как для with. Тем не менее, of иногда является важной частью имен других методов в Eclipse Collections API.

// Bag API - occurrencesOf
MutableBag<String> bag = Bags.mutable.with("1", "2", "3");
Assert.assertEquals(1, bag.occurrencesOf("2"));
// List API - indexOf
MutableList<String> list = Lists.mutable.with("1", "2", "3");
Assert.assertEquals(1, list.indexOf("2"));
// RichIterable API - sumOfInt, sumOfLong, sumOfFloat, sumOfDouble 
MutableList<String> list = Lists.mutable.with("1", "2", "3");
long sum = list.sumOfInt(Integer::parseInt);
Assert.assertEquals(6L, sum);
// RichIterable API - selectInstancesOf
MutableList<String> list = Lists.mutable.with("1", "2", "3");
MutableList<String> filtered = list.selectInstancesOf(String.class);
Assert.assertEquals(list, filtered);

Месть с

With стал более распространенным в API коллекций Eclipse, когда он использовался для дополнения существующих методов, таких как select, reject, collect и т. Д. Методы With в интерфейсе RichIterable изначально были добавлены в качестве оптимизации. Они позволили нам сделать анонимные внутренние классы статическими, предоставив больше возможностей для их полного отсутствия состояния. В качестве полностью независимого и случайного преимущества With методы предоставляют нам больше возможностей для использования ссылок на методы с API коллекций Eclipse. Это хорошо, потому что у меня есть Предпочтение ссылки на метод. Вот несколько примеров использования некоторых из этих методов со ссылками на методы с использованием домена из Eclipse Collections Pet Kata.

boolean any =
        this.people.anySatisfyWith(Person::hasPet, PetType.CAT);
Assert.assertTrue(any);

boolean all =
        this.people.allSatisfyWith(Person::hasPet, PetType.CAT);
Assert.assertFalse(all);

boolean none =
        this.people.noneSatisfyWith(Person::hasPet, PetType.CAT);
Assert.assertFalse(none);

Person found =
        this.people.detectWith(Person::hasPet, PetType.CAT);
Assert.assertNotNull(found);

int count =
        this.people.countWith(Person::hasPet, PetType.CAT);
Assert.assertEquals(2, count);

MutableList<Person> selected =
        this.people.selectWith(Person::hasPet, PetType.CAT);
MutableList<Person> rejected =
        this.people.rejectWith(Person::hasPet, PetType.CAT);
PartitionMutableList<Person> partition =
        this.people.partitionWith(Person::hasPet, PetType.CAT);
Assert.assertEquals(selected, partition.getSelected());
Assert.assertEquals(rejected, partition.getRejected());

Хороший дизайн API - это сложно, потому что сложно именовать. Это прекрасное чувство, когда вы открываете и используете имя, которое ясно передает намерение другим разработчикам. Лучший способ сделать это - использовать ваши имена другими разработчиками, с которыми вы работаете, чтобы достичь консенсуса, прежде чем выбирать имя. В очень редких случаях, когда консенсус невозможен (например, две одинаково хорошие альтернативы), либо просто выберите победителя, либо возьмите на себя расходы по предоставлению обоих. Я почти всегда предпочитаю просто выбрать победителя и двигаться дальше. Мы надеемся, что предоставление заводских методов of и with будет редким исключением.

Я руководитель проекта и ответственный за проект OSS Коллекции Eclipse в Eclipse Foundation. Eclipse Collections открыта для пожертвований. Если вам нравится библиотека, вы можете сообщить нам об этом, отметив ее на GitHub.