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分

华为OD机试真题 Java 实现实力差距最小总和2023 Q1 | 200分

java 用正则 筛选掉特殊符号

SEO之友情链接的作用及筛选技巧