对象与静态方法设计

时间:2009-11-24 12:33:30

标签: java

如下图所示,我可以通过两种简单的方式制作流式复印机(介绍Apache Commons或类似的)。我应该去哪一个,为什么?

public class StreamCopier {
private int bufferSize;

public StreamCopier() {
    this(4096);
}

public StreamCopier(int bufferSize) {
    this.bufferSize = bufferSize;
}

public long copy(InputStream in , OutputStream out ) throws IOException{
    byte[] buffer = new byte[bufferSize];
    int bytesRead;
    long totalBytes = 0;
    while((bytesRead= in.read(buffer)) != -1) {
        out.write(buffer,0,bytesRead);
        totalBytes += bytesRead;
    }

    return totalBytes;
}
}

vs

 public class StreamCopier {

 public static long copy(InputStream in , OutputStream out)
     throws IOException {
     return this.copy(in,out,4096);
 }

 public static long copy(InputStream in , OutputStream out,int bufferSize)
     throws IOException {
     byte[] buffer = new byte[bufferSize];
     int bytesRead;
     long totalBytes = 0;
     while ((bytesRead= in.read(buffer)) != -1) {
         out.write(buffer,0,bytesRead);
         totalBytes += bytesRead;
     }

     return totalBytes;
}
}

12 个答案:

答案 0 :(得分:20)

我会使用非静态(实例)版本,并将其作为与setter的显式依赖关系提供给消费者:

  • 模拟它进行单元测试是微不足道的,所以消费者的测试并没有与实现相结合;
  • 交换功能很简单,例如:使用子类;
  • 与依赖注入系统很好地配合使用。

修改

回应一个(有用的!)评论“这有助于嘲笑?”,这是它的工作原理:

class ThingThatUsesStreamCopier {

    // our copier instance. set in constructor, but might equally use
    // a setter for this:
    private StreamCopier copier;

    public ThingThatUsesStreamCopier(StreamCopier copier) {
        this.copier = copier;
    }

    public void makeCopy(Stream in, Stream out) {
        // probably something a little less trivial...
        copier.copy(in, out);
    }
}

当我来测试ThingThatUsesStreamCopier时,我可以创建StreamCopier的{​​{3}}版本并使用此模拟实例化ThingThatUsesStreamCopier

通过这样做,我可以完全控制我的模拟行为,因此我的测试与StreamCopier的任何实际实现分离。我只测试消费者,而不是消费者和消费者。

答案 1 :(得分:11)

我会选择静态版本,因为没有状态。

无状态对象通常没有意义,除非你需要它用于继承(虚方法)。

如果用户可能想要模拟功能,那么我更喜欢接口而不是具体实现 - 接口的实现者不能是静态的,所以在这种情况下你必须使用实例化的对象。

编辑:几年后,我现在希望谴责我以前的自我,建议使用静态版本。这些天我会毫不犹豫地选择实例版本。

答案 2 :(得分:6)

我会使用静态版本。

由于您没有存储状态,因此不需要对象,因此为什么要让调用者创建一个对象来调用方法。

答案 3 :(得分:2)

这一切都取决于使用模式。也许你只是需要时不时地从InputStream复制到OutputStream?然后它可能无关紧要。但是,如果您在各种环境(网络流,LAN和WAN,复制本地磁盘上的文件)中进行大量复制,则可以选择用于复制的缓冲区大小。

那么,为什么要限制自己只使用一种方法呢?使用对象方法和带有缓冲区大小的构造函数(用于满足您的不同需求)实现它,并且可能添加一个静态方法来获取一个使用一些默认缓冲区大小的伪单例实例(用于偶尔复制然后)。

答案 4 :(得分:1)

开销的差异最小(静态将在实例的基础上分配一次与分配),特别是考虑到状态由单个int组成。一般来说,我很少会选择静态类,因为它们会使单元测试变得困难。基于类实例而不是静态(分配,调零内存和调用构造函数 - 这些都是快速操作)几乎没有开销,并且由于无法模拟静态,我看不到什么好处。

除此之外,静态类还可以显着增加耦合;只要引用了程序集引用,就可以从任何地方引用静态类。当涉及到重构时,这会导致问题(例如,内部的staitc引用被拖入依赖图等等。)

答案 5 :(得分:1)

public static long copy

您不需要实例来执行该方法,因为您没有存储状态,并且您不打算从中获取子类。

我只想添加final关键字。

这样的辅助方法是类方法与实例方法的良好用法。

答案 6 :(得分:1)

无论此时使用什么,但你应该想一想,将来你需要什么。也许你有一些扩展缓冲操作的计划。我选择非静态方法。

答案 7 :(得分:1)

如果你选择静态,你应该避免使用WET名称。

WET代表两次写入所有内容,而不是StreamCopier.copy将其称为

Copy.stream(in,out)

这样你的代码更像是英文。

答案 8 :(得分:0)

由于我没有看到任何重大的性能差异,我认为这只是一个实用性问题,在我看来,静态方法更实用; - )。

答案 9 :(得分:0)

这取决于用法。调用副本的代码是否知道适当的缓冲区大小是什么?可能最好在该代码之外作出决定,并且StreamCopier实例作为参数而不是缓冲区大小传递更好(例如,如果事实证明需要额外参数稍后阶段不需要更改代码)

答案 10 :(得分:0)

静态方法意味着编码为具体类,而不是接口。这意味着更紧密的耦合并使单元测试更难。这就是'它包含国家'规则落空的地方。

答案 11 :(得分:0)

当您使用静态方法时,只要它返回相同的对象就会调用它,但是创建new A()它会在您使用它时创建新的对象