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?
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.