我见过一种用于此类事情的特殊模式,它是 Bloch 的 Typesafe Heterogenous Container 模式的变体。我不知道它是否有自己的名字,但由于没有更好的名字,我将它称为 Typesafe Enumerated Lookup Keys。
基本上,我在各种情况下看到的一个问题是,您需要一组动态的键/值对,其中键的特定子集是“众所周知的”,具有预定义的语义。此外,每个键都与特定类型相关联。
“显而易见”的解决方案是使用枚举。例如,您可以这样做:
public enum LookupKey { FOO, BAR }
public final class Repository {
private final Map<LookupKey, Object> data = new HashMap<>();
public void put(LookupKey key, Object value) {
data.put(key, value);
}
public Object get(LookupKey key) {
return data.get(key);
}
}
这工作得很好,但明显的缺点是现在你需要到处施法。例如,假设您知道LookupKey.FOO 始终具有String 值,而LookupKey.BAR 始终具有Integer 值。你如何执行?有了这个实现,你就不行了。
另外:在这个实现中,键的集合由枚举固定。您不能在运行时添加新的。对于某些应用程序来说这是一个优势,但在其他情况下,您确实希望在某些情况下允许使用新密钥。
这两个问题的解决方案基本相同:使LookupKey 成为一流的实体,而不仅仅是枚举。例如:
/**
* A key that knows its own name and type.
*/
public final class LookupKey<T> {
// These are the "enumerated" keys:
public static final LookupKey<String> FOO = new LookupKey<>("FOO", String.class);
public static final LookupKey<Integer> BAR = new LookupKey<>("BAR", Integer.class);
private final String name;
private final Class<T> type;
public LookupKey(String name, Class<T> type) {
this.name = name;
this.type = type;
}
/**
* Returns the name of this key.
*/
public String name() {
return name;
}
@Override
public String toString() {
return name;
}
/**
* Cast an arbitrary object to the type of this key.
*
* @param object an arbitrary object, retrieved from a Map for example.
* @throws ClassCastException if the argument is the wrong type.
*/
public T cast(Object object) {
return type.cast(object);
}
// not shown: equals() and hashCode() implementations
}
这已经使我们大部分时间到达那里。您可以参考LookupKey.FOO 和LookupKey.BAR,它们的行为与您期望的一样,但它们也知道相应的查找类型。您还可以通过创建 LookupKey 的新实例来定义自己的密钥。
如果我们想实现一些不错的类似枚举的能力,比如静态values() 方法,我们只需要添加一个注册表。作为奖励,如果我们添加注册表,我们甚至不需要 equals() 和 hashCode(),因为我们现在可以按身份比较查找键。
这是该类最终的样子:
/**
* A key that knows its own name and type.
*/
public final class LookupKey<T> {
// This is the registry of all known keys.
// (It needs to be declared first because the create() function needs it.)
private static final Map<String, LookupKey<?>> knownKeys = new HashMap<>();
// These are the "enumerated" keys:
public static final LookupKey<String> FOO = create("FOO", String.class);
public static final LookupKey<Integer> BAR = create("BAR", Integer.class);
/**
* Create and register a new key. If a key with the same name and type
* already exists, it is returned instead (Flywheel Pattern).
*
* @param name A name to uniquely identify this key.
* @param type The type of data associated with this key.
* @throws IllegalStateException if a key with the same name but a different
* type was already registered.
*/
public static <T> LookupKey<T> create(String name, Class<T> type) {
synchronized (knownKeys) {
LookupKey<?> existing = knownKeys.get(name);
if (existing != null) {
if (existing.type != type) {
throw new IllegalStateException(
"Incompatible definition of " + name);
}
@SuppressWarnings("unchecked") // our invariant ensures this is safe
LookupKey<T> uncheckedCast = (LookupKey<T>) existing;
return uncheckedCast;
}
LookupKey<T> key = new LookupKey<>(name, type);
knownKeys.put(name, key);
return key;
}
}
/**
* Returns a list of all the currently known lookup keys.
*/
public static List<LookupKey<?>> values() {
synchronized (knownKeys) {
return Collections.unmodifiableList(
new ArrayList<>(knownKeys.values()));
}
}
private final String name;
private final Class<T> type;
// Private constructor. Only the create method should call this.
private LookupKey(String name, Class<T> type) {
this.name = name;
this.type = type;
}
/**
* Returns the name of this key.
*/
public String name() {
return name;
}
@Override
public String toString() {
return name;
}
/**
* Cast an arbitrary object to the type of this key.
*
* @param object an arbitrary object, retrieved from a Map for example.
* @throws ClassCastException if the argument is the wrong type.
*/
public T cast(Object object) {
return type.cast(object);
}
}
现在LookupKey.values() 的工作方式或多或少类似于枚举。您也可以添加自己的密钥,然后values() 将返回它们:
LookupKey<Double> myKey = LookupKey.create("CUSTOM_DATA", Double.class);
一旦你有了这个 LookupKey 类,你现在就可以实现一个类型安全的存储库,它使用这些键进行查找:
/**
* A repository of data that can be looked up using a {@link LookupKey}.
*/
public final class Repository {
private final Map<LookupKey<?>, Object> data = new HashMap<>();
/**
* Set a value in the repository.
*
* @param <T> The type of data that is being stored.
* @param key The key that identifies the value.
* @param value The corresponding value.
*/
public <T> void put(LookupKey<T> key, T value) {
data.put(key, value);
}
/**
* Gets a value from this repository.
*
* @param <T> The type of the value identified by the key.
* @param key The key that identifies the desired value.
*/
public <T> T get(LookupKey<T> key) {
return key.cast(data.get(key));
}
}