2016-11-23 2 views
3

ПРИМЕЧАНИЕ. У меня создается впечатление, что вы хотите избежать логических утверждений внутри циклов. Это, как мне кажется, частично объясняется тем, как компилятор может оптимизировать любой цикл, где итерации ведут себя предсказуемо. Хотя я почти уверен, что слышал это раньше и долгое время думал об этом как о конвенции. К сожалению, я не мог найти хороших ссылок. Если это правда, однако существуют некоторые противоречивые случаи из-за принципа «СУХОЙ» (не повторяйте себя).Логические внутренние петли? Или, скорее, две отдельные почти идентичные петли?

PRETEXT: Предполагая, что у вас довольно значительный набор данных, скажем, многомерный массив, как я использовал в этом примере. Кроме того, предположим, что вам нужно пройти каждую запись и выполнить некоторую операцию на всех или некоторых элементах, и вам нужно будет выбрать ту, что должна быть выполнена. Это требует создания двух методов, где 90% -99% кода идентичны между двумя, а другой оператор или вызов метода отличается. Если бы это был C++, я бы хотел предоставить указатель на функцию цикла, хотя я не знаю, будет ли это тоже предпочтительнее избегать.

ВОПРОС: Было бы предпочтительным использовать логические утверждения и иметь только один цикл или, скорее, два почти одинаковых метода?

Пример: я обеспечил некоторый пример, чтобы показать, как излишние "близнец" метод решение выглядит:

// This method is provided for completeness of the example 
// and to provide some clue as to what boolean parameter and logic statement 
// I could alternatively have implemented within the loop method instead of external to it. 
public int[][] addOrSubtractArrays(int[][] a, int[][] b, boolean useAdd){ 
    if(a == null || b == null || a.length != b.length || a.length < 1 || a[0].length != b.length) 
     return null; 
    return useAdd ? add(a, b) : subtract(a, b); 
} 

private int[][] add(int[][] a, int[][] b){ 
    int h = a.length; 
    int w = a[0].length; 
    int[][] c = new int[h][w]; 
    for(int y = 0; y < h; y++){ 
     for(int x = 0; x < w; x++){ 
      c[y][x] = a[y][x] + b[y][x]; 
     } 
    } 
    return c; 
} 

private int[][] subtract(int[][] a, int[][] b){ 
    int h = a.length; 
    int w = a[0].length; 
    int[][] c = new int[h][w]; 
    for(int y = 0; y < h; y++){ 
     for(int x = 0; x < w; x++){ 
      c[y][x] = a[y][x] - b[y][x]; 
     } 
    } 
    return c; 
} 

Пример 2: (? Очевидно) Альтернатива

private int[][] addOrSubtract(int[][] a, int[][] b, boolean useAdd){ 
    if(a == null || b == null || a.length != b.length || a.length < 1 || a[0].length != b.length) 
     return null; 
    int h = a.length; 
    int w = a[0].length; 
    int[][] c = new int[h][w]; 
    for(int y = 0; y < h; y++){ 
     for(int x = 0; x < w; x++){ 
      if(useAdd) 
       c[y][x] = a[y][x] + b[y][x]; 
      else 
       c[y][x] = a[y][x] - b[y][x]; 
     } 
    } 
    return c; 
} 

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

+0

Не могу ответить на ваш вопрос, но я бы использовал первый пример с двумя функциями. Вы можете повторить немного кода, но позже он упрощает чтение кода при использовании этих функций. Не нужно искать то, что означает булев. – Mats391

ответ

1

addOrSubtract плохо. Как насчет других форм арифметики, например, умножения? Вы можете выставить multiplyOrDivide, но что, если наступит время, когда выбор должен быть addOrMultiply? Не очень хорошо масштабируется.

То, что вы должны иметь это метод, который позволяет клиенту определять, какую операцию необходимо выполнить:

public int[][] calculate(int[][] first, int[][] second, Operation operation) { 
    if(firstArray == null || secondArray == null || firstArray.length != secondArray.length || firstArray.length < 1 || firstArray[0].length != secondArray.length) 
     throw new IllegalArgumentException("Arrays can't be null and must be of equal length."); 

    int height = firstArray.length; 
    int width = firstArray[0].length; 
    int[][] result = new int[height][width]; 
    for(int y = 0; y < height; y++){ 
     for(int x = 0; x < width; x++){ 
      result[y][x] = operation.performOn(firstArray[y][x], secondArray[y][x]); 
     } 
    } 
} 

Теперь вы можете добавлять новые операции по мере необходимости:

enum Operation { 
    ADD { 
     @Override 
     public void performOn(int firstValue, int secondValue) { 
      return firstValue + secondValue; 
     } 
    }, 
    SUBTRACT { 
     //... 
    }; 

    public abstract int performOn(int firstValue, int secondValue); 
} 

Если вы чувствуете, перекрывая в этот способ делает масштабирование слишком подробным, вы можете использовать шаблон стратегии, делегируя логику функции обратного вызова:

enum Operation { //could/should implement IOperation 
    ADD((a, b) -> a + b), 
    SUBTRACT((a, b) -> a - b); 

    private IOperation operation; 

    Operation(IOperation operation) { 
     this.operation = operation; 
    } 

    public final int performOn(int firstValue, int secondValue) { 
     return operation.performOn(firstValue, secondValue); 
    } 
} 

interface IOperation { 
    int performOn(int firstValue, int secondValue); 
} 

клиент теперь может использовать функцию, как показано ниже:

calculate(firstArray, secondArray, Operation.ADD); 

Поэтому я решил создать новый функциональный интерфейс, а не использовать BiFunction, чтобы избежать Autoboxing. Производительность, похоже, беспокоит вас, и автообновление может резко повлиять на производительность, особенно если вы будете выполнять это интенсивно. Будь то из массивов с большими размерами или из-за необходимости постоянно звонить в addOrSubtract в течение небольшого промежутка времени, лучше избегать ловушки.

IllegalArgumentException позволяет программе «взорвать» описательное сообщение. Вы вернули null, а это значит, что тот, кто использует этот метод, должен выполнить проверку нулевой отметки (беспорядок-некоторый, запах кода, billion dollar mistake), или они могут столкнуться с NullPointerException, у которого нет описательного сообщения.

+0

Я никогда не использовал IOperation, но синтаксис, который он использует, связан ли он с лямбда-выражениями? И возврат null был просто для того, чтобы избежать чрезмерного использования примера, а не того, что я бы сделал в реальном коде. – Chexxor

+1

Да, это функциональный интерфейс, который позволяет создавать лямбда-выражения в любом месте этого интерфейса. Я определил 'IOperation', поэтому вы не найдете его в JDK, но вы всегда можете определить свой собственный интерфейс или использовать' BiFunction', если вы не хотите создавать свои собственные. Использование 'BiFunction' может быть довольно дорогостоящим в вашей ситуации, хотя из-за автобоксинга. Опять же, это предварительная оптимизация, и если вы действительно обеспокоены эффектами использования «BiFunction», вы должны сравнить эти различия. –

2

Как вы уже сказали, если это C++, вы должны передать указатель на функцию. Ну, есть что-то подобное, если вы используете Java 8 - BiFunction<Integer, Integer, Integer>

Ваш единственный метод будет выглядеть следующим образом:

private int[][] addOrSubtract(int[][] a, int[][] b, boolean useAdd){ 
    BiFunction<Integer, Integer, Integer> func = useAdd ? 
     ((a, b) -> a + b) : ((a, b) -> a - b); 
    if(a == null || b == null || a.length != b.length || a.length < 1 || a[0].length != b.length) 
     return null; 
    int h = a.length; 
    int w = a[0].length; 
    int[][] c = new int[h][w]; 
    for(int y = 0; y < h; y++){ 
     for(int x = 0; x < w; x++){ 
      c[y][x] = func.apply(a[y][x], b[y][x]); 
     } 
    } 
    return c; 
} 

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

+0

Эй, уборочная машина, это может быть неуместно для ответа, но можете ли вы объяснить «преждевременную оптимизацию»? я googled это, это цитата, но все еще не может получить то, что это означает точно :) спасибо! – Yazan

+1

Это в основном означает, что не следует делать оптимизацию, прежде чем все остальное будет сделано. Я просто предупреждаю OP, что делает ваш проект _first_, беспокоиться об этом позже. @Yazan – Sweeper

+0

okkki я получил это :), не переполняйтесь оптимизацией, пока ваш проект все еще не завершен, выглядит хорошо для меня :) – Yazan

1

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

public int[][] addOrSubtractArrays(int[][] a, int[][] b, boolean useAdd){ 
    if(a == null || b == null || a.length != b.length || a.length < 1 || a[0].length != b.length) 
     return null; 
    int h = a.length; 
    int w = a[0].length; 
    int[][] c = new int[h][w]; 
    for(int y = 0; y < h; y++){ 
     for(int x = 0; x < w; x++){ 
      c[y][x] = getResult(a[y][x] , b[y][x], useAdd); 
     } 
    } 

    return c; 
} 

private int getResult(int elementA, int elementB, boolean useAdd){ 
    return useAdd ? (elementA+elementB) : (elementA-elementB); 
} 
+0

Итак, передав логическую часть отдельному методу, все равно сделайте компилятор способен оптимизировать петли в одинаковой степени? Или вызов функции внутри цикла сделает для менее оптимизированного кода? Этот вопрос является частью сути моей проблемы. – Chexxor

+1

@Chexxor Я не думаю, что это прямой подход, который может быть заключен на основе поведения компилятора в определенном случае, этот случай очень прост (add или sub), что, если тело for loop имеет больше вещей? более сложная логика ... то, что вы можете сделать, это поместить все вышеперечисленные методы в класс, скомпилировать его, использовать инструмент для декомпилирования файла '.class' и посмотреть, был ли код изменен компилятором, если NOT, то он либо очень эффективен, либо нуждается в ~, но ~ не может быть оптимизирован или просто не заметит различий (и снова: может ли это изменить разницу с другой логикой в ​​цикле?) – Yazan

+0

@Язан Я считаю, что он говорит о оптимизации компиляции во время выполнения (из компилятора [JIT] (http://stackoverflow.com/questions/95635/what-does-a-just-in-time-jit-compiler-do)). Чтение байт-кода не определяет оптимизацию времени выполнения. Имейте в виду, что в игре есть 2 компилятора: 'javac', который компилирует Java в Bytecode и' jit', который компилирует байт-код в собственный код (или просто интерпретирует байт-код). –

Смежные вопросы