Java对象匹配以及权重筛选的设计和实现
Posted c.
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java对象匹配以及权重筛选的设计和实现相关的知识,希望对你有一定的参考价值。
Java对象匹配以及权重筛选的设计和实现
我们先谈需求,再谈设计。
现在我们有一个对象,属于类A和另一堆对象属于类B,他们不是同一个类。但是他们有一些字段是类似的,在我们要从一堆类B的对象中找到和对象A最匹配的对象B。
所以我们需要从一堆类B对象中找到和对象A最匹配的对象。但是即使是最匹配也不一定是符合要求的,所以我们的原则就是既是符合原则的,又是最匹配的,而且有些字段如果匹配的话,对象的相似度会更高一些,也就是更容易命中。
所以我们把需求分为两步
- 找出所有符合需求的类B对象
- 从这对符合的需求的对象列表中找到最匹配的对象B
- 当然还会引申出有些字段匹配的话,他的匹配度会更高,所以这里涉及到字段的权重分配,重要的字段权重会更高
所以从代码逻辑中我们其实就是分为两步
- 找到所有符合需求的对象列表
- 计算这个对象列表中所有的对象的字段权重总和,选择一个权重最大的对象作为最匹配的对象。
接下来就是设计阶段了。
抽取比较的通用DTO
因为前面说了,两个类其实他们只有一些字段是需要比较,而且两个类并不是全部字段都一样。所以我们需要抽取一个通用的比较的DTO出来, 这个DTO中包含我们需要比较的字段。这里就不一一列举全部的字段了。
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Slf4j
public class DimensionCompareDTO
private String name;
//...
所以到时候我们需要比较的时候,只需要把对象A转化成DimensionCompareDTO
对象,把对象B转化成DimensionCompareDTO
对象,然后就是两个一样的类的对象进行比较了。
比较策略的设计和实现
然后就是每个字段其实比较逻辑是不一样的,有的字段如果是全等的话就算是匹配,有的是包含关系也算是匹配,所以每个字段其实都可以有不同的匹配逻辑。那如果全部字段的匹配逻辑都通过if…else的方式去写,那么到时候就会有很多if…else语句,而且可能有些字段的匹配逻辑还是一致的,就不好复用。比如如下:
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Slf4j
public class DimensionCompareDTO
private String name;
//...
boolean isMatch(DimensionCompareDTO dimensionCompareDTO)
boolean isMatch = true;
if(this.name != dimensionCompareDTO.getName()
isMatch = false;
else if(....) // 不同的字段,可能比较的逻辑不一样
//....
isMatch = false;
else if(...)
//....
isMatch = false;
//以后如果有新的匹配字段,就需要加入更多的逻辑,这样的代码复用性低而且太多的if...else语句了,后期不好维护
return isMatch;
这部分我们可以怎么去优化呢,首先我们可以把if…else 变成策略类去优化,对于不同的字段就会有不同的策略,如果有些字段的匹配逻辑相同那就可以用相同的策略去做。 接下来的问题就是怎么让不同的字段对应上不同的策略呢? 答案就是使用注解来实现,在字段上使用注解挂载不同的策略,这样不同的字段就有不同的策略。
那我们先来抽取比较策略的接口了。
public interface CompareStrategy
boolean isCompareTrue(Object v1, Object v2);// v1和v2是相同字段的两个值
之后我们就可以实现我们具体的实现类了,比如下面这些策略了。
这里我们就那其中一个具体的实现类来讲解吧。
public enum EqualsIgnoreCaseCompareStrategy implements CompareStrategy
INSTANCE;
@Override
public boolean isCompareTrue(Object v1, Object v2)
if (Objects.isNull(v1) || Objects.isNull(v2)
|| StringUtils.isBlank(String.valueOf(v1)) || StringUtils.isBlank(String.valueOf(v2)))
return false;
if (Objects.equals(v1, v2))
return true;
return StringUtils.equalsIgnoreCase(StringUtils.trim(String.valueOf(v1))
, StringUtils.trim(String.valueOf(v2)));
这里有的人就开始好奇了,这个策略类怎么是一个枚举类,这里就是一个巧用了,我们都知道使用策略的时候,肯定是是根据不同的类型来生成不同的策略类对象了,比如当我们知道我们的策略类是EqualsIgnoreCaseCompareStrategy
,那我们就需要创建EqualsIgnoreCaseCompareStrategy
对象,然后在调用他的isCompareTrue
方法。 这里存在的问题就是会反复创建策略类对象。所以我们的想法就是如果策略类是单例的话,那我们每次拿到的都是同一个对象,就不需要反复创建了。而通过枚举类就可以很简单的创建一个单例对象。所以正是利用了这种巧妙的方法,我们实现了一个单例的策略实现类。
比较注解的设计和实现
接下来我们设计字段上的注解了
注解上需要满足几个要求了。
- 可以自定义每个字段匹配时候的权重值
- 自定义筛选出符合条件的对象的策略
- 自定义筛选出权重最大的对象的策略
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface Comparison
long weight() default 1000L; //每个字段匹配时候的权重值
Class<? extends CompareStrategy>[] compareStrategies() default EqualsIgnoreCaseCompareStrategy.class, ContainsAnyIgnoreCaseStrategy.class;//从符合的对象列表中找出最大权重的策略
Class<? extends CompareStrategy>[] filterStrategies() default EmptyCaseOrEqualsCaseOrContainCasesCompareStrategy.class; //筛选出符合条件的对象的策略
具体比较和筛选逻辑的设计和实现
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.List;
import java.util.Objects;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.util.ReflectionUtils;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Slf4j
public class DimensionCompareDTO
@Comparison
private String name;
//返回比较之后的权重,通过方法对过滤出符合条件的所有对象进行权重计算,最后只要拿出权重最大的那个对象即可
public Long getTotalWeight(DimensionCompareDTO dimensionCompareDTO)
List<Field> fieldsWithAnnotation = ReflexUtils.getFieldsWithAnnotation(this, Comparison.class);
BigDecimal totalWeight = BigDecimal.ZERO;
for (Field field : fieldsWithAnnotation)
Comparison annotation = field.getAnnotation(Comparison.class);
Class<? extends CompareStrategy>[] strategies = annotation.compareStrategies();
if (isCompareTrue(field, dimensionCompareDTO, strategies))
totalWeight = totalWeight.add(BigDecimal.valueOf(field.getAnnotation(Comparison.class).weight()));
return totalWeight.longValue();
// 查看是否符合条件,通过这个方法过滤出符合条件的所有对象
public boolean isMatchAll(DimensionCompareDTO dimensionCompareDTO)
List<Field> fieldsWithAnnotation = ReflexUtils.getFieldsWithAnnotation(this, Comparison.class);
boolean isAllMatch = true;
for (Field field : fieldsWithAnnotation)
Comparison annotation = field.getAnnotation(Comparison.class);
Class<? extends CompareStrategy>[] strategies = annotation.filterStrategies();
isAllMatch = isCompareTrue(field, dimensionCompareDTO, strategies);
if (!isAllMatch)
return false;
return true;
//比对方法,其实就是调用策略类中的isCompareTrue方法,如果全部都返回true,则为true,有一个是false则为false
private boolean isCompareTrue(Field field, DimensionCompareDTO dimensionCompareDTO, Class<? extends CompareStrategy>[] strategies)
boolean isCompareTrue = false;
PropertyDescriptor propertyDescriptor = BeanUtils.getPropertyDescriptor(this.getClass(), field.getName());
Object costProvisionValue = ReflectionUtils.invokeMethod(Objects.requireNonNull(propertyDescriptor).getReadMethod(), this);
Object billDetailValue = ReflectionUtils.invokeMethod(Objects.requireNonNull(propertyDescriptor).getReadMethod(), dimensionCompareDTO);
for (Class<? extends CompareStrategy> strategy : strategies)
try
isCompareTrue = isCompareTrue || strategy.getEnumConstants()[0].isCompareTrue(costProvisionValue, billDetailValue); //这里就是反射枚举类,然后调用枚举策略类中的isCompareTrue方法,这里拿到的枚举策略类都是单例的。
catch (Exception e)
log.error("compare dimension [] with exception:", field.getName(), e.getMessage());
return isCompareTrue;
Java对象匹配以及权重筛选的设计和实现的介绍就到这里,这里只是提供一些设计思路和实现方法,并不是最佳实践,仅供参考,谢谢。
以上是关于Java对象匹配以及权重筛选的设计和实现的主要内容,如果未能解决你的问题,请参考以下文章
华为OD机试真题 Java 实现实力差距最小总和2023 Q1 | 200分
华为OD机试真题 Java 实现实力差距最小总和2023 Q1 | 200分