循环内的逻辑?或者更确切地说是两个独立的几乎相同的循环?

Logic inside loops? Or rather two separate almost identical loops?

注意:我的印象是您想避免循环内的逻辑语句。我认为之所以这样说,部分原因是编译器如何优化迭代行为可预测的任何循环。虽然我几乎可以肯定我以前听过这个,而且听了很长时间,但我一直认为这是一个惯例。遗憾的是我找不到任何好的参考资料。如果这是真的,但是由于 "DRY" 原则(不要重复自己),存在一些冲突的情况。

PRETEXT:假设您有一个相当大的数据集,比如我在本例中使用的多维数组。此外假设您需要遍历每个条目并对所有或部分元素执行某些操作,并且您需要能够选择要执行的一个或另一个操作。这需要制作两种方法,其中 90%-99% 的代码在两者之间是相同的,而只有运算符或方法调用不同。如果这是 C++,我会想提供一个指向循环函数的函数指针,尽管我不知道这是否也是最好避免的。

问题:使用逻辑语句并且只有一个循环还是两个几乎相同的方法更好?

示例:我提供了一些示例来说明 "twin" 方法解决方案看起来有多么冗余:

// 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;
}

我非常想创建一些包含整个循环结构的通用方法来避免(几乎)重复代码。但是,如果 "what I've heard" 有一些合理的上下文,据我所知最好避免这种情况。

如您所说,如果这是 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,那么我想您可以使用两种解决方案中的任何一种。我不认为他们有什么问题。请记住,过早优化是万恶之源!

这可能超出了您的问题范围,因为我没有答案循环中的逻辑是否良好但是考虑其他设计总是好的。 像这个,没有重复代码,也没有for循环中的逻辑

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);
}

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 的原因是为了避免自动装箱。性能似乎是您的担忧,自动装箱会极大地影响性能,尤其是如果您要高强度地执行此操作时。无论是来自具有大尺寸的数组,还是需要在短时间内连续调用 addOrSubtract,最好避免这个陷阱。

IllegalArgumentException 允许程序 "blow up" 带有描述性消息。您返回了 null,这意味着使用此方法的任何人都需要执行空检查(有些混乱,代码味道,billion dollar mistake),否则他们可能会遇到 NullPointerException 没有描述性消息。