对象与静态方法设计
如下所示,我可以通过两种简单的方式制作流式复印机(介绍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; } }
我将使用非静态(实例)版本,并将其作为与setter的显式依赖关系提供给消费者:
- 因此,对unit testing进行嘲弄是微不足道的,因此对消费者的测试并没有与实现相结合;
- 交换function很简单,例如:使用子类;
- 与dependency injection系统很好地配合。
编辑
回应一个(有用的!)评论“这有助于嘲笑?”,这是它的工作方式:
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
的模拟对象版本,并使用这个模拟实例化ThingThatUsesStreamCopier
。
通过这样做,我可以完全控制我的模拟行为,因此我的测试与StreamCopier
任何实际实现分离。 我只测试消费者,而不是消费者和消费者。
我会选择静态版本,因为没有状态。
除非您需要用于inheritance目的(虚方法),否则无状态对象通常没有意义。
如果用户可能想要模拟function,那么我更喜欢接口而不是具体实现 – 接口的实现者不能是静态的,所以在这种情况下你必须使用实例化的对象。
编辑:几年后,我现在希望谴责我以前的自我建议静态版本。 这些天我会毫不犹豫地选择实例版本。
我会选择静态版本。
由于您没有存储状态,因此不需要对象,因此为什么要让调用者创建一个对象来调用方法。
这一切都取决于使用模式。 也许你只需要偶尔从InputStream复制一些东西到OutputStream ? 然后它可能无关紧要。 但是,如果您在各种环境(网络流,LAN和WAN,本地磁盘上复制文件)中进行大量复制,则可以选择用于复制的缓冲区大小。
那么,为什么要限制自己只使用一种方法呢? 使用对象方法和采用缓冲区大小的构造函数(用于满足您的不同需求)来实现它,并且可能添加一个静态方法来获取使用一些默认缓冲区大小的伪单例实例(用于偶尔复制然后)。
开销的差异最小(静态将在实例的基础上分配一次与分配),特别是在状态由单个int组成的情况下。 一般来说,我很少会选择静态类,因为它们会使unit testing变得困难。 基于类实例而不是静态(分配,调零内存和调用构造函数 – 这些都是快速操作)几乎没有开销,并且由于无法模拟静态,我看不到什么好处。
除此之外,静态类还可以显着增加耦合; 只要引用了程序集引用,就可以从任何地方引用静态类。 当涉及到重构时,这可能导致问题(例如,任何内部的staitc引用被拖入依赖图等)。
public static long copy
您不需要实例来执行该方法,因为您没有存储状态,并且您不打算从中获取子类。
我只想添加final
关键字。
像这样的辅助方法是类方法与实例方法的良好用法。
无论在这个时候用什么,但是你想想,将来你需要什么。 也许你有som计划扩展缓冲操作或其他东西。 我会选择非静态方法。
如果你去静态,你应该避免使用WET名称。
WET代表写入所有内容两次,因此而不是StreamCopier.copy
调用它
Copy.stream(in,out)
那样你的代码就像英文一样。
由于我没有看到任何重大的性能差异,我认为这只是一个实用性问题,在我看来静态方法更实用;-)。
这取决于使用情况。 调用副本的代码是否知道适当的缓冲区大小是多少? 可能是在该代码之外做出更好的决定,并且StreamCopier
实例作为参数传递比缓冲区大小更好(例如,如果事实certificate在稍后阶段需要额外的参数则需要更改代码)
静态方法意味着编码到具体类,而不是接口。 这意味着更紧密的耦合并使unit testing更难。 这就是’它包含国家’规则落空的地方。
当你使用静态方法时,你会在它返回相同的对象时调用它,但你创建new A()
它会在你使用它时创建新的对象