将公共 API 转换为内部粘合代码时避免强制转换
因此,我的应用程序公开了这个公共 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
}
现在假设在内部,Key
有一些不应该公开的属性 - 比如说数据库 ID。 我目前正在做的是这样的:
/** Internal impl of external Key */
class InternalKey implements Key {
int getDatabaseId() {
// do something...
}
}
/** Internal impl of external KVPService */
class InternalKVPService implements KVPService {
// ...
public Object getValue(Key k) {
InternalKey ik = (InternalKey) k;
return getValueFromDBOrWherever(ik.getDatabaseId());
}
// ...
}
这似乎不太理想。 有什么方法可以重新安排职责以避免强制转换并仍然保持内部/外部封装?
请注意,在现实世界中,情况比这要复杂一些; Key
等价物附加了相当多的元数据,除了获取一个简单的值之外,人们可能还想用它来做很多事情,所以只需公开,比如说,Map.Entry<相反,类似 /code> 的对象不一定能解决问题。
我唯一能想到的就是将内部键和外部键完全分开,并保留一个 Map
。 但在这种情况下,我必须复制元数据,这违反了 DRY,或者将外部 Key
委托给 InternalKey
,在这种情况下 Map< /code> 违反了 DRY。
有人能想出更聪明的办法吗?
So, I have this public API that my application exposes, allowing customers to write plug-ins. For the sake of this example, let's say it's a fairly simple key-value pair system, something like:
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
}
Now let's say that internally, the Key
has some attributes that shouldn't be exposed -- say a database ID. What I'm currently doing is something like this:
/** Internal impl of external Key */
class InternalKey implements Key {
int getDatabaseId() {
// do something...
}
}
/** Internal impl of external KVPService */
class InternalKVPService implements KVPService {
// ...
public Object getValue(Key k) {
InternalKey ik = (InternalKey) k;
return getValueFromDBOrWherever(ik.getDatabaseId());
}
// ...
}
This seems less than ideal. Is there some way I can rearrange the responsibilities to avoid the cast and still maintain the inside/outside encapsulation?
Note that in the real world it's a fair bit more complicated than this; the Key
equivalents have a fair bit of metadata attached and there's a number of things someone might want to do with one other than get a simple value, so just exposing, say, Map.Entry
-like objects instead won't necessarily solve the problem.
The only thing I've been able to come up with is to separate the internal and external keys entirely, and keep around a Map<Key, InternalKey>
. But in that case I'd have to either copy the metadata, which violates DRY, or have the external Key
delegate to InternalKey
, in which case the Map
violates DRY.
Can anyone think of something more clever?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我见过的一种方法是为所有对象(在本例中为键)公开一个公共接口,并提供一个基本实现,该实现只需为每个方法抛出
UnsupportedOperationException
(或不执行任何操作)。 然后子类实现随后重写方法以提供功能。 虽然它不太像 OO,但您会在 JDK API 中找到一些示例(例如Iterator
的remove()
方法)。另一种选择:您可以使用 visitor 模式让每个对象执行功能而无需向下转换; 例如,
这里的缺点是您必须公开
InternalKey
上的方法(至少通过接口)以允许您的访问者实现调用它们。 但是,您仍然可以将实现细节保留在包级别。One approach I've seen is to expose a common interface for all objects (in this case keys) and provide a base implementation that simply throws an
UnsupportedOperationException
(or do nothing) for each method. Then sub-class implementations subsequently override method(s) to provide functionality. Granted it's not very OO-like but you'll find some examples in the JDK API (e.g.Iterator
'sremove()
method).Another option: You could use the visitor pattern to have each object perform functionality without downcasting; e.g.
The disadvantage here is that you would have to expose the methods on
InternalKey
(at least via an interface) to allow your visitor implementations to call them. However, you could still keep the implementation detail at the package level.我不这么认为。 但为什么类型转换会让你担心呢? 潜在的 ClassCastException 在实践中不会发生(假设我了解您的用例),转换只需要 5 条左右的机器指令,并且它对 KPService API 的调用者是隐藏的。
I don't think so. But why does the typecast worries you? The potential ClassCastException won't happen in practice (assuming that I understand your use-case), the cast will only take 5 or so machine instructions, and it is hidden from callers of your KPService API.