假设我有一个具有以下结构的键/值。
@Value.Immutable
public interface Key {
EnumFoo required_enum_one;
EnumBar required_enum_two;
Optional<boolean> optional_boolean_one;
Optional<EnumBaz> optional_enum_two;
}
@Value.Immutable
public interface Value {
Message message;
}
假设我具有键/值映射,其中对于必填字段的全部和可选字段的 some 组合,都具有消息值。例如:
{foo1, bar1, true, baz1 } => msg1
{foo1, bar1, true, <any value except baz1> } => msg2
{foo1, bar2, <any value>, <any value> } => msg3
{foo2, bar2, <any value>, <any value> } => msg4
If I do a look up of {foo1, bar2, false, baz1}, it should resolve to msg3
实现此目标的最佳方法是什么?我正在考虑向键添加一个自定义@equals,当映射集合中不存在该键时,它会跳过可选匹配。至于映射集合,我正在考虑一个键/值元组列表,该列表是根据可选字段的存在而排序的(类似于上面的代码块),以便第一个匹配 all 必填字段和最个可选字段被选中并返回相应的值?
有没有更好的方法?作为奖励,我发现自己对不同的类型重复了这种模式。有没有办法可以重用?
答案 0 :(得分:2)
过去,我通过将“地图的键”(一个java.util.Map
)与“键类型”(您在q中定义的接口)分开考虑,解决了这个问题
我已经使用StringListMapKey做到了这一点,它类似于:
final class StringListMapKey {
private List<String> keyParts;
public StringListMapKey(List<String> keyParts) {
this.keyParts = keyParts;
}
@Override
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof StringListMapKey)) {
return false;
}
return this.keyParts.equals(((StringListMapKey) obj).keyParts);
}
@Override
public int hashCode() {
return keyParts.hashCode();
}
}
然后,您可以采用Key类型的实例(问题中的那个实例),然后使用类似的方法将其转换为StringListMapKey-您可以在其中放入众所周知的/静态映射:
final class MapKeyFactory {
private static final String ANY = "$ANY";
public StringListMapKey createFrom(Key key) {
List<String> keyParts = new ArrayList<>();
keyParts.add(key.getEnumFoo()
.toString());
keyParts.add(key.getEnumBar()
.toString());
keyParts.add(deriveMaybeBooleanOneKeyPart(key.isMaybeBooleanOne()));
keyParts.add(derviceMaybeEnumBazKeyPart(key.getMaybeEnumBaz()));
return new StringListMapKey(keyParts);
}
private String derviceMaybeEnumBazKeyPart(Optional<EnumBaz> maybeEnumBaz) {
if (!maybeEnumBaz.isPresent()) {
return ANY;
}
EnumBaz enumBaz = maybeEnumBaz.get();
switch (enumBaz) {
case BAZ1:
return "ONE";
default:
return ANY;
}
}
private String deriveMaybeBooleanOneKeyPart(Optional<Boolean> maybeBooleanOne) {
if (!maybeBooleanOne.isPresent()) {
return ANY;
}
Boolean booleanOne = maybeBooleanOne.get();
if (booleanOne) {
return "TRUE";
}
return ANY;
}
}
执行此操作的唯一问题是您会在实际的地图密钥中丢失类型信息(因为它是引擎盖下的List<String>
)。因此,在需要获取实际键中的基础数据的情况下,我将值设置为等于Pair<Key, Message>
。
在此,理性提示您的类型Key
实际上(或可能是!)应该检查其实际字段值是否相等-而不是派生的值。
因此,您最终拥有一个Map
,看起来像这样:Map<StringListMapKey, Pair<Key, Message>>
。
它不大也不聪明,但是它为我提供了很好的服务,并且通用性强,可以用于其他可能要缓存并进行快速查找的复杂条件。
如果考虑性能(很多哈希/等式),则可以考虑使用“ DelimitiedStringMapKey”将您的Key
实例转换为简单的旧字符串,例如“ foo1:bar1:$ ANY: $ ANY”。
答案 1 :(得分:1)
我建议您使用“责任链设计模式”。您可以考虑将“关键条件”创建为链中的一个节点。每个节点都有其评估给定标准的方式。
https://sourcemaking.com/design_patterns/chain_of_responsibility
答案 2 :(得分:1)
您可以使用流将键(别名配置)与地图中的键(别名规则)进行匹配。这是我执行的命令:
class Matcher {
Map<Key, Value> rules;
Matcher(Map<Key, Value> rules)
{
this.rules = rules;
}
public Optional<Message> findMessage(Key configuration)
{
return rules.keySet().stream()
.filter(e -> e.required_enum_one == configuration.required_enum_one)
.filter(e -> e.required_enum_two == configuration.required_enum_two)
.filter(e -> ! e.optional_boolean_one.isPresent() || e.optional_boolean_one.equals(configuration.optional_boolean_one))
.filter(e -> ! e.optional_enum_two.isPresent() || e.optional_enum_two.equals(configuration.optional_enum_two))
.findFirst()
.map(e -> rules.get(e).message);
}
}
// Test
Matcher matcher = new Matcher(rules);
Key configuration1 = new Key(foo1, bar1, Optional.of(true), Optional.of(baz1));
System.out.println("configuration1: " + matcher.findMessage(configuration1));
Key configuration2 = new Key(foo1, bar1, Optional.of(true), Optional.of(baz2));
System.out.println("configuration2: " + matcher.findMessage(configuration2));
Key configuration3 = new Key(foo1, bar2, Optional.of(false), Optional.of(baz2));
System.out.println("configuration3: " + matcher.findMessage(configuration3));
Key configuration4 = new Key(foo2, bar2, Optional.of(true), Optional.of(baz1));
System.out.println("configuration4: " + matcher.findMessage(configuration4));
Key configuration5 = new Key(foo2, bar1, Optional.of(true), Optional.of(baz1));
System.out.println("configuration5: " + matcher.findMessage(configuration5));
输出为:
configuration1: Optional[Message=msg1]
configuration2: Optional[Message=msg2]
configuration3: Optional[Message=msg3]
configuration4: Optional[Message=msg4]
configuration5: Optional.empty
这是带有所有类定义和对象初始化的样板代码,以使示例运行:
class EnumBar {
}
class EnumFoo {
}
class EnumBaz {
}
class Message {
String text;
Message(String text) {
this.text = text;
}
public String toString() {
return "Message=" + text;
}
}
class Key {
EnumFoo required_enum_one;
EnumBar required_enum_two;
Optional<Boolean> optional_boolean_one;
Optional<EnumBaz> optional_enum_two;
Key(EnumFoo required_enum_one, EnumBar required_enum_two,
Optional<Boolean> optional_boolean_one, Optional<EnumBaz> optional_enum_two) {
this.required_enum_one = required_enum_one;
this.required_enum_two = required_enum_two;
this.optional_boolean_one = optional_boolean_one;
this.optional_enum_two = optional_enum_two;
}
}
class Value {
Message message;
Value(Message message) {
this.message = message;
}
}
EnumBar bar1 = new EnumBar();
EnumBar bar2 = new EnumBar();
EnumFoo foo1 = new EnumFoo();
EnumFoo foo2 = new EnumFoo();
EnumBaz baz1 = new EnumBaz();
EnumBaz baz2 = new EnumBaz();
Message msg1 = new Message("msg1");
Message msg2 = new Message("msg2");
Message msg3 = new Message("msg3");
Message msg4 = new Message("msg4");
Optional<Boolean> anyBooleanValue = Optional.empty();
Optional<EnumBaz> anyBazValue = Optional.empty();
final Map<Key, Value> rules = new HashMap<>();
rules.put(new Key(foo1, bar1, Optional.of(true), Optional.of(baz1)), new Value(msg1));
rules.put(new Key(foo1, bar1, Optional.of(true), anyBazValue), new Value(msg2));
rules.put(new Key(foo1, bar2, anyBooleanValue, anyBazValue), new Value(msg3));
rules.put(new Key(foo2, bar2, anyBooleanValue, anyBazValue), new Value(msg4));
只有一个缺点:映射是无序的条目列表,因此,如果您获取keySet并对其进行流式传输,则可以在第二条规则之前评估第二条规则。 (这意味着在我们的示例中configuration1可以输出消息2)。这只是流的时间问题和集的实现问题。 您可以通过使用有序列表而不是映射来解决此问题,但是您不能并行评估所有规则以节省性能(这意味着使用“ .parallelStream()”而不是“ .stream()”)。我有一个更好的主意:只需将优先级字段添加到您的规则(键类)中即可。然后在优先级上使用“ .max(...)”,而不是应用“ .first()”。