Java对象匹配以及权重筛选的设计和实现

该文章介绍了如何设计和实现Java对象匹配及权重筛选的方案。通过抽取通用DTO进行字段比较,使用比较策略接口结合注解实现不同字段的定制化比较逻辑,并利用枚举策略类确保单例。此外,还详细阐述了比较和筛选逻辑,以找到最匹配的对象。
摘要由CSDN通过智能技术生成

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对象匹配以及权重筛选的设计和实现的介绍就到这里,这里只是提供一些设计思路和实现方法,并不是最佳实践,仅供参考,谢谢。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值