СУХОЙ: Минимизация повторяющегося кода в Java - PullRequest
7 голосов
/ 27 января 2011

Я пишу метод на Java:

List<Foo> computeFooList(/* arguments */)
{
    /* snip */
}

Я хотел бы написать второй метод с точно такой же логикой, но с другим типом возврата:

List<String> computeStringList(/* same arguments */)
{
    /* snip */
}

Я пытаюсь найти нехакерский способ минимизировать количество повторяющегося кода между двумя методами.Логическое различие между ними * заключается в том, что при добавлении объекта в возвращаемый список первый метод добавляет значение Foo:

List<Foo> computeFooList(/* arguments */)
{
    List<Foo> toReturn = ...
    ...
    for (Foo foo : /* some other list of Foo */)
    {
        if (/* some condition */)
        {
            toReturn.add(foo);
        }
    }
    ...
    return toReturn;
}

, а второй -String представление Foo:

List<String> computeStringList(/* same arguments */)
{
    List<String> toReturn = ...
    ...
    for (Foo foo : /* some other list of Foo */)
    {
        if (/* some condition */)
        {
            toReturn.add(foo.toString());
        }
    }
    ...
}

На самом деле, это не совсем 1018 *, что просто.Я не хочу добавлять Foo к toReturn, если я не уверен, что он там есть.В результате это решение принимается за foo с использованием вспомогательных функций.С двумя разными версиями методов мне также потребуются разные версии вспомогательных функций - в конце я бы написал два набора почти одинаковых методов, но для одного небольшого универсального типа.


Могу ли я написать единственный метод, который содержит всю логику принятия решений, но может генерировать или List<Foo> или List<String>?Возможно ли это сделать без использования необработанных List типов (плохая практика в универсальных землях!) Или подстановочных знаков List<?> типов?Я представляю реализацию, которая выглядит примерно так:

List<Foo> computeFooList(/* args */)
{
    return computeEitherList(/* args */, Foo.class);
}

List<String> computeStringList(/* args */)
{
    return computeEitherList(/* args */, String.class);
}

private List<???> computeEitherList(/* args */, Class<?> whichType)
{
    /* snip */
}

Есть ли какой-нибудь хороший, элегантный способ сделать это? Я играл с универсальными методами, но я могу 'не вижу способа сделать это.Даже гадость с отражением никуда меня не привела (может быть, мне нужно что-то вроде TypeToken? ... eww).

Ответы [ 3 ]

7 голосов
/ 27 января 2011

Не можете ли вы преобразовать логику преобразования в отдельную стратегию (например, Function<F, T> в Guava):

public <T> List<T> computeList(/* arguments */, Function<? super Foo, T> f) {
     List<T> toReturn = ...     ...     
     for (Foo foo : /* some other list of Foo */) {
         if (/* some condition */) {
             toReturn.add(f.apply(foo));
         }
     }
     return toReturn;
} 

computeFooList:

computeList(..., Functions.identity());

computeStringList:

computeList(..., Functions.toStringFunction());
0 голосов
/ 27 января 2011

Это немного уродливо, но я думаю, что это может сработать:

List<Foo> computeFooList(/* args */) {
    return computeEitherList(/* args */, Foo.class);
}

List<String> computeStringList(/* args */) {
    return computeEitherList(/* args */, String.class);
}

private <T> List<T> computeEitherList(/* args */, Class<T> whichType) {
    List<T> rval = new ArrayList<T>();
    for (Foo foo : listOfFoo) {
        // process foo

        if (whichType.equals(Foo.class)) {
            rval.add(whichType.cast(foo));
        }
        else if (whichType.equals(String.class)) {
            rval.add(whichType.cast(foo.toString()));
        }
        else {
            throw new SomeException("Cannot compute list for type " + whichType);
        }
    }
    return rval;
}
0 голосов
/ 27 января 2011

У меня есть интерфейс "SearchFilter" и абстрактный класс "FilterAdapter", который я использую аналогичным образом. Логика принятия решений может быть принята независимо и общим способом от фактического добавления вещей в список возврата. Я бы проверял каждый Foo и говорил, что «true» включает его или «false» исключает его.

public interface SearchFilter<T>
{
    public boolean include(T item);
    public Collection<T> apply(Collection<T> items);
}

Фильтр можно применить к существующей коллекции с помощью метода apply(), возвращая новую коллекцию, которая содержит только нужные элементы.

newCollection = myfilter.apply(originalItems);

Это может быть бесполезно для вас, но концепция include() должна хорошо работать, чтобы не повторять логику принятия решения.

У вас может быть FooFilter extends FilterAdapter<Foo> (я также иногда создаю их анонимно in-line), который обеспечивает реализацию include

public FooFilter extends FilterAdapter<Foo>
{
    public boolean include(Foo item)
    {
        if (item.isInvalid()) return false;
        // or any other complex logic you want
        return item.hasGoodThings();
    }
}

Метод apply() почти всегда просто перебирает исходную коллекцию и тестирует include, поэтому он имеет реализацию по умолчанию в моем FilterAdapter, но его можно переопределить.

...