Java对象的对比

创建时间:2019/3/17 下午8:33:57
编辑时间:2019/3/17 下午8:33:57
作者: huww98@163.com (huww98@163.com)
分类:Java

近日我有个这样的需求,对比同一个类的两个对象中的内容。一个对象是来自数据库的,另一个是接收自http请求的。根据对比的结果我可以判断用户有没有修改其中的内容,修改的部分需不需要重新审核之类的。这个对象中还有一个列表需要对比,对比的方式是对比列表中的元素的ID和原来的是否一样。但也不是所有的字段都需要对比。例如数据库中的

如果我直接按照最容易想到的办法,逐字段的比较当然也可以,但是会就会导致大量看上去很冗余的代码。类似这样:

if (!Objects.equals(oldDesc.getTitle(), newDesc.getTitle()) ||
    !Objects.equals(oldDesc.getDetail(), newDesc.getDetail()) || ...) {
    return Diff.NEED_REVIEW;
}

一是同样的getXxx操作要分别对两个对象写两遍,当我们在其中还要引入null判断呀,归一化之类的操作的时候,这就很讨厌了。二是当新增了新的属性后,该对比方法也需要对应修改,我感觉不太符合“开闭原则”,容易在维护时造成问题。

为此,我找了一个object-differ库,它可以对比两个对象。当然,要实现我上面所说的所有需求,我们需要对它进行一些配置。我使用了一个叫GoodsDescriptionPart的Annotation来注明是否要对比。Identified接口定义了getId函数。

this.differ = ObjectDifferBuilder.startBuilding()
	.inclusion().resolveUsing(new InclusionResolver() {
		@Override
		public Inclusion getInclusion(DiffNode node) {
			if (node.getPath().getLastElementSelector() instanceof CollectionItemElementSelector) {
				return Inclusion.INCLUDED;
			}
			GoodsDescriptionPart annotation = node.getFieldAnnotation(GoodsDescriptionPart.class);
			if (annotation == null) {
				return Inclusion.EXCLUDED;
			}
			return Inclusion.INCLUDED;
		}

		@Override
		public boolean enablesStrictIncludeMode() {
			return true;
		}
	}).and()
	.comparison().ofType(Identified.class).toUseEqualsMethodOfValueProvidedByMethod("getId").and()
	.identity().setDefaultCollectionItemIdentityStrategy((working, base) -> {
		if (working instanceof Identified && base instanceof Identified) {
			return ((Identified) working).getId().equals(((Identified) base).getId());
		}
		return false;
	}).and()
	.build();

可以看到,配置的内容也变得非常繁杂了,而且最后也还没有完全达成我的需求,还有些小问题,不仅如此,这些奇怪的配置使得调试变得很困难,也增加了不少后人理解上的负担。最后我还是放弃了这种方法。

目前我使用的方法是利用了Java的Functional interface。虽然没有解决上述问题二,但自我感觉还算是不错。毕竟像问题二这样的情况有时候确实不容易解决,也不太值得付出太多代价去解决它。

List<Function<GoodsDescription, Object>> needReview = List.of(
	GoodsDescription::getTitle,
	GoodsDescription::getDetail,
	GoodsDescription::getContactInfo,
	d -> d.getPhotos().stream().map(Photo::getId).collect(Collectors.toSet()));
List<Function<GoodsDescription, Object>> updates = List.of(
	GoodsDescription::getBuyingPrice,
	GoodsDescription::getSellingPrice,
	GoodsDescription::getArea,
	GoodsDescription::getActive,
	d -> d.getCategory() == null ? null : d.getCategory().getId());
for (var a : needReview) {
	if (!Objects.equals(a.apply(oldDesc), a.apply(newDesc))) {
		return Diff.NEED_REVIEW;
	}
}
for (var a : updates) {
	if (!Objects.equals(a.apply(oldDesc), a.apply(newDesc))) {
		return Diff.UPDATED;
	}
}
return Diff.NO_CHANGE;

非空检查、归一化、列表毕竟都能够作为一个Function保存下来并应用到不同的对象上,整个代码也比较简短,比较令我满意。

我还能想到一些其他的方案,比如,把这些需要检查、需要审核的字段单独拿出来放到不同的对象里,重写各组的equals方法,再把这些对象组合在一起。但这样又会带来很多其他的问题,比如数据库持久化的映射、json序列化和反序列化,另外为了访问方便,还要多写不少getter和setter吧。我觉得这个问题并不值得用这样的方案来解决,就像不值得用上面介绍的object-differ库一样,虽然这个库的确强大且灵活。

工程便是一个这样的过程吧,有的时候其实并没有什么对与错,有的只是作为设计者的我们,如何去权衡各种方案的利弊得失。


返回文章列表

评论

登录 / 注册 后发布评论