Избежание приведений при переводе общедоступных API-интерфейсов во внутренний код клея - PullRequest
3 голосов
/ 07 августа 2009

Итак, у меня есть этот публичный 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>. Но в этом случае я должен был бы либо скопировать метаданные, которые нарушают DRY, либо иметь внешний делегат Key для InternalKey, и в этом случае Map нарушает DRY.

Кто-нибудь может придумать что-нибудь более умное?

Ответы [ 2 ]

1 голос
/ 07 августа 2009

Один подход, который я видел, состоит в том, чтобы предоставить общий интерфейс для всех объектов (в данном случае ключей) и предоставить базовую реализацию, которая просто выбрасывает UnsupportedOperationException (или ничего не делает) для каждого метода. Затем реализации подкласса впоследствии переопределяют метод (ы) для обеспечения функциональности. Конечно, он не очень похож на OO, но вы найдете несколько примеров в JDK API (например, метод Iterator remove()).

Другой вариант: вы можете использовать шаблон visitor , чтобы каждый объект выполнял свою функциональность без снижения рейтинга; например, * +1008 *

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 (по крайней мере, через интерфейс), чтобы позволить реализации ваших посетителей вызывать их. Тем не менее, вы все равно можете сохранить детализацию реализации на уровне пакета.

1 голос
/ 07 августа 2009

Я так не думаю. Но почему ты беспокоишься? Потенциальная исключительная ситуация ClassCastException не будет реализована на практике (при условии, что я понимаю ваш сценарий использования), приведение команды займет всего около 5 машинных инструкций и будет скрыто от абонентов вашего API KPService.

...