Если нельзя, но очень хочется, то нужно обязательно и ничего в мире не стоит того, чтобы делать из этого проблему!


Интересна Java? Кликай по ссылке и регистрируйся!
Если тебе полезно что-то из того, чем я делюсь в своем блоге - можешь поделиться своими деньгами со мной.
с пожеланием
столько времени читатели провели на блоге - 
сейчас онлайн - 

воскресенье, 11 ноября 2012 г.

Refactoring: Извлечение итератора из цикла for

Решаю задачку. Наколбасил кода. И в один прекрасный момент немного засутпорился. Есть метод
Он чудный :) но не об этом сейчас. Нужно очень быстро сделать так, чтобы в зависимости от boolean direction параметра метод итерировался по второму циклу (int x) в прямом, либо в обратном порядке (но в том же диапазоне).

Копипаст и if с двумя циклами не катит - дублирование. Выделить внутренности цикла int x тоже не катит - много зависимостей. Да и не интересно - я так уже делал в молодости :)

Некрасивый код стал еще более некрасивым.

Как быть? И тут приходит идея. А что если поспользоваться интерфейсом Iterable? По нему for ходит без пороблем. Выглядеть это будет приблизительно так.

А вот и сам класец. Только внимание, я его сделал таким, что о сам определяет где from где to даже если ты их перепутал местами - то немного неочевидно, фор не работает так, но мне удобно!

public class XIterator implements Iterable<Integer>{
    private int from;
    private int to;
    private int x;
    private boolean increase;

    public XIterator(int from, int to, boolean increase) {
        if (increase) {
            this.from = Math.min(from, to);
            this.to = Math.max(from, to);
        } else {
            this.from = Math.max(from, to);
            this.to = Math.min(from, to);
        }

        this.increase = increase;
        this.x = this.from;
    }

    @Override
    public Iterator<Integer> iterator() {
        return new Iterator<Integer>() {
            @Override
            public boolean hasNext() {
                if (increase) {
                    return x <= to;
                } else {
                    return x >= to;
                }
            }

            @Override
            public Integer next() {
                if (increase) {
                    return x++;
                } else {
                    return x--;
                }
            }

            @Override
            public void remove() {
                throw new UnsupportedOperationException();
            }
        };
    }
}
Вот тесты для него
public class XIteratorTest {

    @Test
    public void increase() {
        assertIteratorValues(new XIterator(0, 10, true), "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]");
    }

    @Test
    public void decrease() {
        assertIteratorValues(new XIterator(0, 10, false), "[10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]");
    }

    @Test
    public void increaseNegative() {
        assertIteratorValues(new XIterator(-5, 5, true), "[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5]");
    }

    private void assertIteratorValues(Iterable<Integer> iterator, String expected) {
        LinkedList list = new LinkedList();
        for (Integer i : iterator) {
            list.add(i);
        }

        assertEquals(expected, list.toString());
    }

    @Test
    public void decreaseNegative() {
        assertIteratorValues(new XIterator(-5, 5, false), "[5, 4, 3, 2, 1, 0, -1, -2, -3, -4, -5]");
    }

    @Test
    public void increaseBadFromTo() {
        assertIteratorValues(new XIterator(5, -5, true), "[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5]");
    }

    @Test
    public void decreaseBadFromTo() {
        assertIteratorValues(new XIterator(5, -5, false), "[5, 4, 3, 2, 1, 0, -1, -2, -3, -4, -5]");
    }

    @Test
    public void zeroIterator() {
        assertIteratorValues(new XIterator(0, 0, false), "[0]");
        assertIteratorValues(new XIterator(0, 0, true), "[0]");
    }
}

4 комментария:

  1. Да... тяжело же в жабе без linq. Печаль:(

    ОтветитьУдалить
  2. Саша, а я не считаю вызов метода в двух местах дублированием кода. То, что ты с нуля написал итератор, является большим злом, как по мне.
    Посчитай сам: кол-во потенциальных ошибок и проблем при "дублировании" вызовов метода и написании нового велосипеда вообще не сравнимо.

    Кстати, у апача есть реверсный итератор: http://commons.apache.org/collections/api-release/org/apache/commons/collections/iterators/ReverseListIterator.html

    ОтветитьУдалить
  3. Велосипеды писать стоит. На них учишься. Не поймешь как оно работает, пока сам не разберешь и не соберешь обратно. Эксперименты, вот чего не стоит бояться. Ошибки делать.

    Кстати, студенты наши реализовывают List'ы, Queue вкачестве домашки - откуда у них это желание, как думаешь? :)

    Вообще, как это говорится - сделай это рабочим, сделай это красивым и сделай это быстрым. http://c2.com/cgi/wiki?MakeItWorkMakeItRightMakeItFast
    После того, как написал велосипед стоит найти кого-то, кто это сделал уже давно - у него код оптимальнее и сделать замену реализации.

    А вообще этот код я даже обсуждать бы не стал, я вчера на него смотрел и он казался ужасным, а сегодня вообще молчу.
    Но идея с экстрактом for'a в контексте еще одного типа реаткоринга я заценил. Потому тут образовался этот пост. Уверен, когда-нибудь этот тип рефакторинга будет очень к месту. Люблю их экспериментировать.

    ОтветитьУдалить