【问题标题】:How not to expose a public interface in Java如何不暴露 Java 中的公共接口
【发布时间】:2010-12-03 08:31:07
【问题描述】:

在我的项目jOOQ 中,我使用复杂的数据结构对 SQL 查询进行建模。查询的所有组件都实现了

public interface QueryPart {
  int bind(java.sql.PreparedStatement stmt);
  int bind(java.sql.PreparedStatement stmt, int initialIndex);
  SQLDialect getDialect();
  String toSQLDeclaration();
  String toSQLDeclaration(boolean inlineParameters);
  String toSQLReference();
  String toSQLReference(boolean inlineParameters);
}

该接口的方法被库的所有包在内部用于构造和执行 SQL。不应直接从客户端代码调用它们。为此,我添加了

public interface QueryPartProvider {
  QueryPart getQueryPart();
}

这是唯一公开的接口。实际查询部分的示例是:

public interface Table extends QueryPartProvider {}
class TableImpl implements QueryPart, Table {}

如您所见,QueryPart 方法只能通过Table.getQueryPart().toSQLDeclaration() 等方式访问。

我的设计有助于阻止直接访问 QueryPart 方法,但不能完全隐藏它。我的问题是:谁能告诉我一个好的设计模式来实现这个目标?

注意:最简单但不是很好的解决方案是将所有对象转换为 QueryPart,例如((QueryPart) table).toSQLDeclaration()

【问题讨论】:

  • 如果你想隐藏它,为什么要添加一个'getQueryPart'方法?
  • 您可以尝试隐藏实现此接口的对象。这是你需要的吗?
  • @Arne:我想从客户端代码中隐藏它,但将它暴露给库。 @Stas:实现接口的对象是包私有的(参见 TableImpl)。这些对象通过接口公开(见表)

标签: java design-patterns interface


【解决方案1】:

接口的所有方法始终是公共的,因此您无法访问您的库客户端也无法访问的内容。 也许您可以使用Table 的抽象类和作为包保护的getQueryPart() 方法来实现您想要的。但是我不确定我是否愿意这样做,而不是从Table 转换为TableImpl

【讨论】:

  • 抽象类不是 Java 的最佳方式,因为不允许多重继承。 QueryPart 的许多实现扩展了其他一些类...
  • 使用抽象类并使抽象方法受保护对我有用...
【解决方案2】:

在实现类似于 sfussenegger 建议的东西之后,我想出了一个更好的解决方案,涉及 Adapter 设计模式。这是总纲:

/**
 * Objects providing an internal API implement this interface
 */
public interface Adapter {

  /**
   * Dynamically expose an (publicly unknown) internal API. 
   */
  <T> T internalAPI(Class<T> internalType) throws ClassCastException;
}

这种适配器类型是唯一向公众公开的关于内部任何事物的事实。只有包私有实现方法才知道此方法的可能参数(以及那些真正想实际使用内部 API 进行变通、扩展等的黑客)。

/**
 * This type contains the public API for a QueryPart
 */
public interface QueryPart extends Adapter {
// [...]
}

/**
 * This type contains the internal API for a QueryPart
 */
public interface QueryPartInternal extends QueryPart {
// [...]
}

上面的QueryPart和QueryPartInternal是相关的。这个事实为公众所知,但没有公共类/类型扩展 QueryPartInternal。只有以下包私有类及其无数子类可以:

/**
 * This class is the base class for all QueryParts.
 * It is package private and thus doesn't expose anything
 */
abstract class AbstractQueryPart implements QueryPartInternal {
  // [...]

  /**
   * For other package private implementation methods
   */
  @Override
  public final <T> internalAPI(Class<T> internalType) {
    return internalType.cast(this);
  }

  /**
   * Convenience method for subclasses heavily using the
   * internal API
   */
  protected final QueryPartInternal internal(QueryPart part) {
    return part.internalAPI(QueryPartInternal.class);
  }
  // [...]
}

【讨论】:

    【解决方案3】:

    您能解释一下您为什么要这样做吗?我能看到的唯一原因是无法为您的库的用户实现接口。

    我认为这不是一个好方法。只需添加一些 Javadoc 并解释为什么实现它没有意义。但最后,留给用户是否有正当理由创建自定义实现。总是很难预见每个用例。

    如果有人坚持他的方法,那当然不是你的错 - 他不能说他没有被警告过 :)

    举个例子,这就是你可以在 Apache Wicket 的源代码中找到的所有内容:

    /**
     * THIS IS WICKET INTERNAL ONLY. DO NOT USE IT.
     * 
     * Traverses all behaviors and calls ...
     */
    

    编辑: 只是另一个:你可以试试这个,虽然我仍然不鼓励它——不要说你没有被警告过;)

    public interface ExposedInterface {
      void foo();
    }
    
    // only default visibility
    interface InternalInterface extends ExposedInterface {
      // nothing here
    }
    
    // and here some methods
    ExposedInterface get(); // user can use it
    
    void set(InternalInterface obj); // user is out of luck here
    

    【讨论】:

    • 正如我所说,我需要库内部的接口方法。我不希望用户实现这个接口。但是如果公共 API 没有以某种方式实现接口,那么库代码将需要一直进行类转换。请参阅带有 TableImpl 和 Table 的示例
    • 啊,现在编辑改变了一些东西。关于Javadoc的好点。我已经这样做了(不是在上面的例子中):-) 很高兴看到 Wicket 的人有同样的想法。
    • @lukas 我添加了更多想法。 “我不希望用户实现这个接口”可以解释为“我认为用户永远不必实现这个接口”,对吧?
    • 是的。用户永远不必实现这个接口
    • @Lukas 好的,这是最后一次尝试
    猜你喜欢
    • 2021-10-25
    • 1970-01-01
    • 2018-06-10
    • 2020-01-31
    • 1970-01-01
    • 2018-09-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多