在具有必填字段和可选字段的对象上进行最佳匹配搜索

exc*_*ray 6 java oop design-patterns

假设我有一个具有以下结构的键/值。

@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; 
} 
Run Code Online (Sandbox Code Playgroud)

假设我有一个键/值映射,其中对于所有必填字段组合和一些可选字段组合都有消息值。例如:

{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
Run Code Online (Sandbox Code Playgroud)

实施此方法的最佳方法是什么?我正在考虑向键添加一个自定义@equals,当映射集合中不存在该键时,它会跳过可选匹配。至于映射集合,我正在考虑一个键/值元组的列表,该列表是根据可选字段的存在而排序的(类似于上面的代码块),以便第一个匹配所有必需字段和大多数可选字段的键选择并返回相应的值?

有没有更好的方法?作为奖励,我发现自己对不同的类型重复了这种模式。有没有办法可以重用?

Not*_* JD 2

我过去已经通过将“地图的键”(a 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,您可以获取 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

这样做的唯一问题是您会丢失实际映射键中的类型信息(因为它是在List<String>引擎盖下)。因此,我将值设置为相当于Pair<Key, Message>,以防我需要获取实际键中的基础数据。

这里的基本原理是,您的类型Key确实(或者:可能!)应该检查其实际字段值是否相等 - 而不是派生值。

所以你最终会得到一个Map看起来像这样的:Map<StringListMapKey, Pair<Key, Message>>

它不大,也不聪明,但它对我很有帮助,并且足够通用,可以用于您可能想要缓存和快速查找的其他复杂标准。

如果性能成为一个考虑因素(这是大量的散列/等于),您可以考虑使用“DelimitiedStringMapKey”,它将您的Key实例转换为普通的旧字符串,如“foo1:bar1:$ANY:$ANY”。