Избегание приведения при переводе общедоступных 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
имеет некоторые атрибуты, которые не должны быть представлены - скажем, идентификатор базы данных. То, что я сейчас делаю, выглядит примерно так:
/** 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
-подобные объекты вместо этого не обязательно решат проблему.
Единственное, что мне удалось придумать, - это полностью отделить внутренние и внешние ключи и сохранить Map<Key, InternalKey>
, Но в этом случае мне придется либо скопировать метаданные, которые нарушают СУХОЙ, либо иметь внешний Key
делегировать InternalKey
в этом случае Map
нарушает СУХОЙ.
Кто-нибудь может придумать что-нибудь более умное?
2 ответа
Один из подходов, которые я видел, состоит в том, чтобы предоставить общий интерфейс для всех объектов (в данном случае ключей) и предоставить базовую реализацию, которая просто выдает UnsupportedOperationException
(или ничего не делать) для каждого метода. Затем реализации подкласса впоследствии переопределяют метод (ы) для обеспечения функциональности. Конечно, он не очень похож на OO, но вы найдете несколько примеров в JDK API (например, Iterator
"s remove()
метод).
Другой вариант: вы можете использовать шаблон посетителя, чтобы каждый объект выполнял свои функции без снижения рейтинга; например
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
(по крайней мере, через интерфейс), чтобы позволить посетителям реализации вызывать их. Тем не менее, вы все равно можете сохранить детали реализации на уровне пакета.
Я так не думаю. Но почему ты беспокоишься? Потенциальная исключительная ситуация ClassCastException не будет реализована на практике (при условии, что я понимаю ваш сценарий использования), приведение команды займет всего около 5 машинных инструкций и будет скрыто от абонентов вашего API KPService.