【问题标题】:Should I add helper/utility methods to my library API in Java?我应该在 Java 中向我的库 API 添加帮助程序/实用程序方法吗?
【发布时间】:2017-07-31 13:03:48
【问题描述】:

我正在用 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 中添加单行方法。
  • 我认为第三个可能是最糟糕的一个,因为如果用户必须阅读ImageDataSource 的Javadoc,那么获取Image 变得太复杂了,然后找到如何创建一个DataSource,我个人也觉得它非常“重量级”(让我想起具有大量类和包的非常大的框架,而我喜欢让我的项目保持小而简洁)。

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

【问题讨论】:

    标签: java api-design


    【解决方案1】:

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

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

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

    从此链接查看第 14 页

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

    【讨论】:

      【解决方案2】:

      你的实际设计看起来真的很好。

      为每种数据源类型提供一个(帮助器)方法,例如 Image.createImage(ByteBuffer), Image.createImage(Path), ... 与 Javadoc 本质上为每个方法重复(这实际上是 目前的情况)

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

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

      public FileInputStream(String name) throws FileNotFoundException {

      public FileInputStream(File file) throws FileNotFoundException {

      公共 FileInputStream(FileDescriptor fdObj) {

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

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

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

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

      并且这些方法的 javadoc 可能总是引用带有 @see{@link} javadoc 注解的常用方法。

      最后,由于每个方法都使用不同的类型作为参数,我们可以很容易地猜出它们之间的差异。

      【讨论】:

        【解决方案3】:

        您已经得到了很好的答案,但是关于 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 ...
        

        【讨论】:

          【解决方案4】:

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

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

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

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

          另一方面,如果您只关心 JavaDoc 描述的重复,您可以随时使用{@link} tag to write it once and refer it everywhere else

          【讨论】:

          • 这是问题还是答案?我真的说不出来。
          • 关于要点 2,您说好处是 API 非常易于使用。在要点 3 中,您更喜欢使用另一种数据类型的更复杂的方法,用户必须将实际数据类型包装到其中。
          • @Matt 它易于使用并不一定意味着我更喜欢它,还有一些负面的方面也必须考虑在内。
          • 对,但是具有 DataSource 超类的类层次结构的好处如何适合他的问题,因为他想从数据类型完全异构(字符串、路径、流、字节)的参数创建图像数组)。
          • @Matt 正如他所建议的那样,底层实现实际上只使用一种形式的输入(我猜是 InputStream),而其他方法只包含必要的转换代码。 DataSource 类可以公开检索 InputStream 所需的方法,并且每个子类都可以执行必要的转换。
          【解决方案5】:

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

          也许将Image 的构造函数设为私有,这样用户就必须使用工厂。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2011-11-08
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-03-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多