我正在用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设计。
我想到了三种不同的方法来重新设计我的Image
API:
- 仅提供采用输入流的单一图像解码方法,例如
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
如果用户必须同时阅读 JavadocImage
和DataSource
,然后找到如何创建一个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类:FileInputStream
、FileOutputStream
。
这些是这样设计的:
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
构造函数设为私有,以便用户需要使用工厂。