简体   繁体   中英

How not to expose a public interface in Java

In my project jOOQ , I model SQL queries with a complex data structure. All components of a query implement

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);
}

This interface's methods are used internally by all packages of the library to construct and execute SQL. They should not be invoked directly from client code. For that purpose, I have added

public interface QueryPartProvider {
  QueryPart getQueryPart();
}

Which is the only publicly exposed interface. An example of an actual query part is:

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

As you can see, the QueryPart methods can only be accessed via Table.getQueryPart().toSQLDeclaration() , etc.

My design helps discouraging direct access to QueryPart methods, but cannot completely hide it. My question is: Can anyone tell me a good design pattern to achieve this goal?

Note: The simplest but not very nice solution would be to cast all objects to QueryPart, eg ((QueryPart) table).toSQLDeclaration()

All methods of an interface are always public, so there is no way for you to have access to something which is not accessible to your library clients as well. Maybe you could achieve what you want using an abstract class for Table , and the getQueryPart() method as package protected. I'm not sure however that I would want to do that, instead of a cast from Table to TableImpl .

After implementing something similar to what sfussenegger suggested, I came up with an even better solution involving the Adapter design pattern . This is the general outline:

/**
 * 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;
}

This adapter type is the only fact exposed to the public about anything internal. Only package private implementation methods know about the possible arguments to this method (and those hackers that really want to actually use the internal API for workarounds, extensions, etc).

/**
 * 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 {
// [...]
}

The above QueryPart and QueryPartInternal are related. This fact is known to public but no public class / type extends QueryPartInternal. Only the following package-private class and its gazillion subclasses do:

/**
 * 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);
  }
  // [...]
}

Could you please explain why you'd like to do that? The only reason I can see is to make it impossible to implement the interface for a user of your library.

I don't think that's a good approach. Simply add some Javadoc and explain why it doesn't make sense to implement it. But finally, leave it to the user whether there's a valid reason to create a custom implementation. It's always difficult to foresee each and every use case.

If somebody gots stuck with his approach it's certainly not your fault - he can't say he hasn't been warned :)

To give an example, that's what you can find all over Apache Wicket's source code:

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

EDIT: just another though: you could try this, although I'd still discourage it - don't say you haven't been warned ;)

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM