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

发布于 2024-08-01 16:19:39 字数 1243 浏览 7 评论 0原文

因此,我的应用程序公开了这个公共 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

如歌彻婉言 2024-08-08 16:19:39

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

另一种选择:您可以使用 visitor 模式让每个对象执行功能而无需向下转换; 例如,

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 上的方法(至少通过接口)以允许您的访问者实现调用它们。 但是,您仍然可以将实现细节保留在包级别。

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's remove() method).

Another option: You could use the visitor pattern to have each object perform functionality without downcasting; 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);
  }
}

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.

喜爱皱眉﹌ 2024-08-08 16:19:39

我不这么认为。 但为什么类型转换会让你担心呢? 潜在的 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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文