代码重用 - Java

3

这些函数中是否有任何重复利用数组迭代代码的方法:

public static double[] ln(double[] z) {
    // returns  an array that consists of the natural logarithm of the values in array z
    int count = 0;

    for (double i : z){
        z[count] = Math.log(i);
        count += 1;
    }
    return z;
}


public static double[] inverse(double[] z) {
    //  returns  an array that consists of the inverse of the values in array z
    int count = 0;

    for (double i : z){
        z[count] = Math.pow(i,-1);
        count += 1;
    }
    return z;
}

1
并不是真的... Java 不是 Lisp ;) - Gugussee
@Gugussee:是的,你可以。在List中,你可能可以通过语言结构来实现这一点,但在Java中,你可以通过面向对象的设计模式来实现。 - cherouvim
1
请注意,这种方式会在给定的数组参数中反映出更改...换句话说,返回它是不必要的,或者您应该在更改它之前创建给定的数组参数的克隆。 - BalusC
@cherouvim:你说的是要么也使用反射,要么将您的公共方法命名为“doIt”(而不是“log”或“inverse”),但无论如何,这都可能比病情更糟。这只是我开玩笑说的话,并不是对Java的批评 ;) - Gugussee
@Gugussee:策略模式中没有使用反射。一切都是类型安全的,采用了稳健的面向对象原则。另外,我并没有批评你的评论,只是在说这是有可能的。 - cherouvim
7个回答

7

是的,可以使用策略模式,但这可能会过度设计。重复迭代和计数++看起来不错。


3

您的代码与注释不符。

public static double[] ln(double[] z) {
    // returns  an array that consists of the natural logarithm of the values in array z
    double[] r = new double[z.length];
    for(int i=0;i<z.length;i++) r[i] = Math.log(z[i]);
    return r;
}

public static double[] inverse(double[] z) {
    //  returns  an array that consists of the inverse of the values in array z
    double[] r = new double[z.length];
    for(int i=0;i<z.length;i++) r[i] = 1/z[i];
    return r;
}

你可以使用策略模式使循环变得通用,但这有三个缺点。
  • 代码更难阅读。
  • 代码更长且更复杂。
  • 执行速度慢了很多倍。
Math.pow(x, -1)比1/x更昂贵,可能会产生更多的舍入误差。

你的实现非常好。谢谢。 - AlexBrand
1
因为它返回一个副本,它不会改变原始值,使得你的编码更简单。这是返回另一个数组的重要原因之一。如果你只想改变原始值,那么就不需要返回值。 - Peter Lawrey

2

这里有一种重复使用循环的方法。以下是一个例子:

public interface MathOperation {
   public double f(double value);
}

public class Invert implements MathOperation {
   public double f(double value) {
     return Math.pow(value, -1);
   }
}

public class Log implements MathOperation {
   public double f(double value) {    
     return Math.log(value);
   }
}

private static void calculateOnArray(double[] doubles, MathOperation operation) {
  int count = 0;

  for (double i : doubles){
    doubles[count] = operation.f(i);
    count += 1;
  }
}

public static double[] ln(double[] z) {
  calculateOnArray(z, new Log());
  return z;
}

public static double[] inverse(double[] z) {
  calculateOnArray(z, new Invert());
  return z;
}

- 这不是命令模式,也不是真正的策略模式 的实现。它接近于策略模式,但为了避免进一步的投票下降,我保持该模式未命名;-)


2
这是策略模式。你正在使用传入的参数,而不是实现自己的状态。如果您将z传递给MathOperation的构造函数,则会成为命令模式。不过,这是个好例子。虽然我可能会将new Log()new Invert()分配为静态字段,而不是每次重新创建它们。 - BalusC
@BalusC - 我真是太蠢了,当然,你是对的!我会修改文本的。 - Andreas Dolk

1

不是很注重重复使用,但有两点需要注意 - 首先,永远不要使用i作为数组值,因为这是约定俗成的迭代器,如果您这样做会让人们感到非常困惑。其次,在此处使用for循环而不是for each循环将带走您的手动“计数”变量。

我还会在返回之前创建数组的副本,您实际上正在更改传递的参数,如果其他人看到您的方法签名,则可能不是他们所期望的。我会期望具有此类返回类型的方法保留原始参数不变(因为没有必要返回我已经引用的相同值!)


1

是的,正如这里的几个例子所说明的那样。但如果这是真正的问题而不是极端简化,创建三个类并使用继承和重载来节省编写一个FOR语句似乎是朝着错误的方向前进。你要编写二十行代码来避免重复一行代码。有什么好处呢?

如果在现实生活中,由于某种原因,迭代数据的过程更加复杂--如果不仅仅是循环遍历数组,而是一些复杂的过程,例如在数据库中查找东西、提交Web服务请求并进行一页充满复杂计算以找到下一个元素--那么我的答案会有所不同。


1
在支持闭包、lambda表达式或可传递的块(例如Ruby)的语言中,你可以简洁地实现这个功能。在Java中,你可以通过定义一个带有回调方法的接口来模拟这个功能。在使用它的地方,你可以创建一个匿名类来实现该接口。看起来有点繁琐:
public interface Calculation {
    double calculate(double x);
}

public static double[] calcArray(double[] z, Calculcation calc) {
    int count = 0;

    for (double i : z){
        z[count] = calc.calculate(i);
        count += 1;
    }
    return z;
}

public static double[] ln(double[] z) {
    return calcArray(z, new Calculation() {
        double calculate(double x) {
            return Math.log(x);
        }
    });
}

public static double[] inverse(double[] z) {
    return calcArray(z, new Calculation() {
        double calculate(double x) {
            return Math.pow(x, -1);
        }
    });
}

顺便提一下 - 闭包将在Java 8中得到语言支持(不仅仅是单方法接口),但那还需要一段时间(2012年!) - Michael Berry

1

如果您使用函数式编程,可以重复使用此代码。有许多库可以为您编写大量的代码。在这个例子中,我只是使用了Guava(http://code.google.com/p/guava-libraries/),但像Functional Java(http://functionaljava.org/)这样的库同样适用。

函数式编程的一个主要优点是能够将算法抽象化,以便您可以在需要的任何地方重复使用该算法。对于您的情况,您的算法基本上是这样的:

  1. 对于列表中的每个元素
  2. 对该元素执行操作x
  3. 返回新列表

关键是能够根据需要传入新的操作来替换“x”。在函数式编程世界中,我们创建一个称为“functor”的对象,它实际上只是一种将函数封装在对象中的方法。(如果Java有闭包,这将更容易,但这就是我们所拥有的。)

无论如何,这里有一些代码可以实现您想要的功能:

类:Inverse


import com.google.common.base.Function;

public class Inverse implements Function
{
    @Override
    public Double apply(Double arg0)
    {
        return Math.pow(arg0, -1);
    }
}

类:对数


import com.google.common.base.Function;

public class Logarithm implements Function
{
    @Override
    public Double apply(Double arg0)
    {
        return Math.log(arg0);
    }
}

类:驱动程序


import static org.junit.Assert.*;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.junit.Test;

import com.google.common.collect.Collections2;

public class Driver
{
    @Test
    public void testInverse()
    {
        List initialValues = Arrays.asList(new Double[] {1.0, 2.0, 3.0});

        List logValues = new ArrayList(Collections2.transform(initialValues, new Inverse()));

        assertEquals(3, logValues.size());

        assertEquals(Double.valueOf(1.0), logValues.get(0), 0.01);
        assertEquals(Double.valueOf(0.5), logValues.get(1), 0.01);
        assertEquals(Double.valueOf(0.333), logValues.get(2), 0.01);

    }
}

Driver类只包含一个测试用例,但它确实说明了上面定义的Functors的使用方法。您会注意到在上面的testInverse方法中使用了一个名为Collections2.transform的方法。您可以在此处找到有关此方法的文档:

http://guava-libraries.googlecode.com/svn/trunk/javadoc/com/google/common/collect/Collections2.html#transform%28java.util.Collection,%20com.google.common.base.Function%29

本质上,这种方法抽象了遍历列表并对每个元素应用某些函数的算法 - 你所要做的就是应用该函数来执行。因此,在这种情况下,我向该方法传递一组Double和一个函数对象(Inverse)。它会给我返回一个包含初始列表中每个值的倒数的新列表。如果您希望执行每个值的对数,则可以将不同的函数对象传递给transform方法。

这种类型的编程确实需要一点学习曲线,但对于您想要重复使用代码的类型来说绝对是很棒的。通过利用现有的库(如Guava),您甚至可以使自己的代码更容易编写。注意,我编写的代码实际上比您写的代码更容易,因为在执行我的数学函数时,我不必处理列表/数组 - 我只需对一个元素执行该函数;transform函数负责将该函数应用于整个列表。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接