如何避免OpenSearch Java客户端TypeMapping构建的if-else代码异味
针对OpenSearch Java客户端映射构建的代码异味解决方案
1. 用策略模式重构getTypeProperty,消灭大量if-else
不要用枚举实现策略,改用职责单一的Handler类+映射Map的方案,完美适配第三方库的异构Builder:
- 定义统一的
PropertyHandler接口,包含主属性和子属性的构建方法(解决子属性处理问题):public interface PropertyHandler { Property.Builder buildProperty(FieldMetadata metadata); Property.Builder buildSubProperty(FieldMetadata subFieldMetadata); } - 为每种
Property.Kind写独立的Handler实现类,比如TextPropertyHandler、NestedPropertyHandler,每个类只负责对应类型的Builder构建逻辑,包括子属性的递归处理(比如Nested类型内部的字段):public class NestedPropertyHandler implements PropertyHandler { @Override public Property.Builder buildProperty(FieldMetadata metadata) { NestedProperty.Builder builder = NestedProperty.builder(); // 配置嵌套类型专属参数,如include_in_parent for (FieldMetadata subField : metadata.getSubFields()) { Property.Builder subProp = HANDLER_MAP.get(subField.getKind()).buildSubProperty(subField); builder.subField(subField.getName(), subProp.build()); } return builder; } @Override public Property.Builder buildSubProperty(FieldMetadata subFieldMetadata) { return buildProperty(subFieldMetadata); } } - 用静态初始化的
EnumMap存储类型与Handler的映射,启动时完成注册:private static final Map<Property.Kind, PropertyHandler> HANDLER_MAP; static { HANDLER_MAP = new EnumMap<>(Property.Kind.class); HANDLER_MAP.put(Property.Kind.TEXT, new TextPropertyHandler()); HANDLER_MAP.put(Property.Kind.KEYWORD, new KeywordPropertyHandler()); HANDLER_MAP.put(Property.Kind.NESTED, new NestedPropertyHandler()); // 其他类型依次添加即可 } - 原
getTypeProperty方法简化为一行映射调用,新增类型只需加Handler和Map条目,完全符合开闭原则:public Property.Builder getTypeProperty(FieldMetadata metadata) { PropertyHandler handler = HANDLER_MAP.get(metadata.getKind()); if (handler == null) { throw new IllegalArgumentException("Unsupported property kind: " + metadata.getKind()); } return handler.buildProperty(metadata); }
2. 抽象工厂统一两种构建入口
针对TypeMappingBuilder和注解式MappingFactory的重复映射逻辑,用抽象工厂统一封装:
- 定义
TypeMappingFactory抽象接口,覆盖两种构建场景:public interface TypeMappingFactory { TypeMapping.Builder buildTypeMapping(Class<?> entityClass); TypeMapping.Builder buildTypeMapping(Map<String, FieldMetadata> fieldMetadataMap); } - 实现两个具体工厂:
AnnotationBasedMappingFactory:负责解析实体类注解,转换为FieldMetadata集合后,复用上面的PropertyHandler构建所有属性;BuilderBasedMappingFactory:封装原生TypeMappingBuilder的链式调用,内部同样通过HANDLER_MAP处理不同类型的属性添加。
- 这样两种构建方式共享同一套属性构建逻辑,新增类型时无需分别修改两个入口,消除重复代码。
3. 为何放弃枚举策略?
枚举策略的问题在于:所有逻辑硬编码在枚举类中,类型增多后会变得极度臃肿;枚举实例是静态单例,无法灵活适配不同的配置场景;而且枚举类无法拆分,违反单一职责原则。而独立的Handler类可以单独测试、单独维护,扩展性更强。
额外优化(Spring环境)
如果是Spring项目,可通过@Component标记所有PropertyHandler,再用@Autowired自动注入到Map中,无需手动维护静态Map的初始化:
@Autowired private Map<Property.Kind, PropertyHandler> handlerMap;
Spring会自动将实现PropertyHandler的Bean以Property.Kind为Key注入,进一步降低维护成本。
内容的提问来源于stack exchange,提问作者contemplator
相关产品推荐
相关产品推荐

