Java 在将公共API转换为内部粘合代码时避免强制转换

Java 在将公共API转换为内部粘合代码时避免强制转换,java,oop,encapsulation,Java,Oop,Encapsulation,因此,我的应用程序公开了这个公共API,允许客户编写插件。在本例中,假设它是一个相当简单的键值对系统,类似于: public interface Key { // marker interface, guaranteed unique in some scope } public interface KVPService { Set<Key> getKeys(); Object getValue(Key k); // must be a key provided by

因此,我的应用程序公开了这个公共API,允许客户编写插件。在本例中,假设它是一个相当简单的键值对系统,类似于:

public interface Key {
  // marker interface, guaranteed unique in some scope
}

public interface KVPService {
  Set<Key> getKeys();
  Object getValue(Key k); // must be a key provided by getKeys() or it blows up
}
这似乎不太理想。我是否有办法重新安排职责,以避免强制转换,同时保持内部/外部封装

请注意,在现实世界中,它比这要复杂得多;等价的
附加了相当多的元数据,除了获取一个简单的值之外,人们可能还想用其中的一个来做很多事情,所以仅仅公开,比如说,
Map.Entry
-类对象不一定能解决问题

我唯一能想到的就是将内部和外部键完全分开,并保留一个
映射
。但是在这种情况下,我必须复制元数据,这违反了DRY,或者让外部
委托给
内部键
,在这种情况下
映射
违反了DRY


有人能想出更聪明的办法吗?

我不这么认为。但是为什么打字会让你担心呢?潜在的ClassCastException在实践中不会发生(假设我了解您的用例),强制转换只需要5条左右的机器指令,并且它对KPService API的调用方是隐藏的。

我看到的一种方法是为所有对象(在本例中为键)公开一个公共接口并提供一个基本实现,该实现仅为每个方法抛出一个
UnsupportedOperationException
(或不执行任何操作)。然后子类实现随后重写方法以提供功能。虽然它不太像OO,但您可以在JDK API中找到一些示例(例如,
Iterator
remove()
方法)

另一个选项:您可以使用该模式让每个对象执行功能,而无需向下转换;e、 g

public interface KeyVisitor {
  void visitInternalKey(InternalKey k);
  void visitFooBarKey(FooBarKey k);
}

public interface Key {
  void visit(KeyVisitor kv);
}

public class InternalKey implements Key {
  public void visit(KeyVisitor kv) {
    kv.visitInternalKey(this);
  }
}

这里的缺点是,您必须公开
InternalKey
上的方法(至少通过一个接口),以允许访问者实现调用它们。但是,您仍然可以在包级别保留实现细节。

我认为主要是我不喜欢这样,我无法在编译时证明
键是
内部键。但是考虑到我从外部调用者那里得到了
s,这样做可能是不合理的。如果你想进行编译时检查,你必须在你的接口上使用类或泛型。假设您可以更改您的公共API。@Robin:即使使用泛型,JVM通常也必须在封面下进行类型转换。我需要做很多不同的事情来
Key
,我不确定直接访问者模式是否可行,但这至少将责任放在了正确的位置。好主意,谢谢。唯一需要记住的是:如果过度使用Visitor,可能会使代码变得相当不可读——有时switch/If-then语句实际上更易于维护(而且可能更快)。
public interface KeyVisitor {
  void visitInternalKey(InternalKey k);
  void visitFooBarKey(FooBarKey k);
}

public interface Key {
  void visit(KeyVisitor kv);
}

public class InternalKey implements Key {
  public void visit(KeyVisitor kv) {
    kv.visitInternalKey(this);
  }
}