我是否应该在 Java 中将帮助程序/实用程序方法添加到我的库 API 中



我正在用Java开发一个小型库,可以从压缩的图像数据源(例如PNG文件)中读取图像并对其进行解码,返回Image对象。Image类具有名为Image.createImage(...)的各种函数,这些函数可以从指定的参数创建图像。

我已经在库 API 中添加了几个公共Image.createImage(...)方法,每种数据源类型一个:Image.createImage(Path)Image.createImage(InputStream)Image.createImage(String)Image.createImage(byte[])、...,让用户可以轻松地从各种数据源获取Image,而无需键入大量代码。

然而,这意味着每个方法的Javadoc都是重复的,并且API变得更大,所以有点难学习,即使帮助程序方法本身是相当微不足道的(例如Image.createImage(byte[])只是return Image.createImage(new ByteArrayInputStream(array));),所以我决定重新设计我的库API设计。

我想到了三种不同的方法来重新设计我的ImageAPI:

  • 仅提供采用输入流的单一图像解码方法,例如Image.createImage(InputStream),并让用户自己从他的数据源创建输入流(也许在 Javadoc 中包含一些示例代码,例如"要解码存储在文件中的图像,您可以使用Files.newInputStream(Paths.get(...))...")
  • 为每个数据源类型提供一个(帮助程序)方法,例如Image.createImage(ByteBuffer)Image.createImage(Path), ...Javadoc 本质上为每个方法复制(这实际上是当前的情况)
  • 设计一个可以从数据源类型之一构造的DataSource类(例如new DataSource(Path),...),并且具有单个图像解码方法,该方法采用DataSource对象,例如Image.createImage(DataSource)

我想知道哪种设计是最好的。

  • 我认为第一个非常好,因为它使库非常小/轻量级,但缺点是用户必须自己编写"胶水代码"(或从 Javadoc 示例中复制它),因此必须知道如何从路径或资源字符串中获取InputStream。我很想选择这个设计。
  • 我认为第二个不是那么好,因为Javadoc变得非常"冗长"/大,可能不值得向API添加单行方法。
  • 我认为第三个很可能是最糟糕的,因为Image如果用户必须同时阅读 JavadocImageDataSource,然后找到如何创建一个DataSource,那么它变得太复杂了,我个人也觉得它非常"重量级"(让我想起了包含大量类和包的非常大的框架, 而我喜欢保持我的项目小而简洁)。

如果要使用此库,您更喜欢哪种设计?第一个设计是最好的吗?

根据约书亚·布洛赫的说法,

API 应尽可能小,但不能更小

• 如有疑问,请省略 ─ 功能、类、方法、 参数等 ─ 您可以随时添加,但永远无法删除

从这个链接看第14页

http://www.cs.bc.edu/~muller/teaching/cs102/s06/lib/pdf/api-design

你的实际设计似乎很好。

为每个数据源类型提供一个(帮助程序)方法,例如 Image.createImage(ByteBuffer), Image.createImage(Path), ...与 Javadoc 基本上为每个方法复制(这实际上是 现状)

API 越简单易用,效果越好。
提供方法重载通常是一个优势,因为客户端不需要记住很多东西来应用处理/创建具有不同风格的对象。
他/她只需要记住the方法。

看看java.io包中的JDK类:FileInputStreamFileOutputStream
这些是这样设计的:

public FileInputStream(String name) 抛出 FileNotFoundException {

public FileInputStream(File file) 抛出 FileNotFoundException {

public FileInputStream(FileDescriptor fdObj) {

当然,如果您定义了许多重载方法,例如 8 个或更多,情况就会有所不同。
您的 API 可能会变得更难阅读和使用,并且可能容易出错。
你似乎不是这样。

为每个方法记录javadoc应该不是问题。
客户不必背诵它们。

此外,它们的javadoc应该非常相似:

  • Image.createImage(Path)
  • Image.createImage(InputStream)
  • Image.createImage(String), -
  • Image.createImage(byte[])

这些方法的javadoc可能总是引用具有@see{@link}javadoc注释的通用方法。

最后,由于每种方法都使用不同的类型作为参数,因此我们可以很容易地猜测两者之间的方差。

你已经得到了很好的答案,但是关于javadoc:只需使用链接到其他方法,使用:

{@link package.class#member label}

意思是:如果你真的有一组"相同"的相关方法需要不同的参数 - 那么不仅要避免代码重复,还要避免文档重复是一种很好的做法。所以:

/**
* This is the extensively documented "anchor" method ...
* @param String path to image as String ...
*/
public void createImage(String path) { ...
/** 
* See {@link ...
* and some additional information ...

从设计的角度来看,这实际上取决于您对库的目标以及您希望将其扩展到多远。

  • 第一个选项创建了一个非常轻量级的库,可以在多种情况下使用,但留给用户很多你知道需要完成的工作,还有一些你并不真正知道。它很容易编写,并且不会限制您可以使用它的方式,但它不是那么容易使用,如果您只想分解您经常使用的代码,它可能真的不值得

  • 如果你保持原样,你将保留一个巨大的(从外部)库非常易于使用。但是,正如其他人提到的,可能难以维护。

  • 在我看来,第三种情况是更面向对象的。你抽象出一个DataSource的概念,你基本上可以创建一个类的层次结构,你需要的每种类型的源都有一个。易于扩展,易于使用(使用适当的工厂方法)。为了创建一个可保留的库,我会使用这种方法。

另一方面,如果你唯一关心的是JavaDoc描述的重复,你总是可以使用{@link}标签来编写它一次,然后在其他地方引用它

该问题似乎与创建有关。 考虑支持多个数据源的ImageFactory。 胶水代码在工厂中,因此只有一个Image构造函数。

也许将Image构造函数设为私有,以便用户需要使用工厂。

最新更新