You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何优化AssertJ中领域模型与DTO的自定义匹配器以减少重复代码?

优化AssertJ自定义匹配器代码重复的方案

针对领域模型与DTO类的AssertJ自定义匹配器存在大量重复代码的问题,提供以下几种优化方案:

方案一:泛型抽象基类(推荐,类型安全)

将通用断言逻辑和模板代码抽象到泛型基类中,子类仅需实现特定类型的字段获取逻辑:

1. 定义泛型抽象基类

import org.assertj.core.api.AbstractAssert;
import org.assertj.core.api.Assertions;

public abstract class AbstractValueAssert<SELF extends AbstractValueAssert<SELF, T>, T> extends AbstractAssert<SELF, T> {

    protected AbstractValueAssert(T actual, Class<?> selfType) {
        super(actual, selfType);
    }

    // 子类需实现该方法,提供从目标对象获取value的逻辑
    protected abstract String extractValue(T actual);

    // 通用的hasValue断言方法
    public SELF hasValue(String expected) {
        Assertions.assertThat(extractValue(actual)).isEqualTo(expected);
        return myself;
    }
}

2. 领域模型匹配器子类

public class ExampleAssert extends AbstractValueAssert<ExampleAssert, Example> {

    private ExampleAssert(Example actual) {
        super(actual, ExampleAssert.class);
    }

    public static ExampleAssert assertThat(Example actual) {
        return new ExampleAssert(actual);
    }

    @Override
    protected String extractValue(Example actual) {
        return actual.getValue();
    }
}

3. DTO匹配器子类

public class ExampleDTOAssert extends AbstractValueAssert<ExampleDTOAssert, ExampleDTO> {

    private ExampleDTOAssert(ExampleDTO actual) {
        super(actual, ExampleDTOAssert.class);
    }

    public static ExampleDTOAssert assertThat(ExampleDTO actual) {
        return new ExampleDTOAssert(actual);
    }

    @Override
    protected String extractValue(ExampleDTO actual) {
        return actual.getValue();
    }
}

该方案完全复用了断言逻辑,子类仅需实现字段提取和基础模板代码,符合AssertJ的流畅API设计风格,且类型安全无反射。

方案二:接口默认方法(适配无法修改基类的场景)

通过定义包含默认断言方法的接口,让两个匹配器类实现该接口以复用逻辑:

1. 定义断言接口

import org.assertj.core.api.Assertions;

public interface HasValueAssert<SELF> {
    // 子类需实现该方法,返回被断言的对象
    Object getActual();

    default SELF hasValue(String expected) {
        // 用AssertJ的extract方法反射获取value字段,无需修改业务类
        Assertions.assertThat(getActual()).extracting("value").isEqualTo(expected);
        return (SELF) this;
    }
}

2. 改造现有匹配器

public class ExampleAssert extends AbstractAssert<ExampleAssert, Example> implements HasValueAssert<ExampleAssert> {

    private ExampleAssert(Example actual) {
        super(actual, ExampleAssert.class);
    }

    public static ExampleAssert assertThat(Example actual) {
        return new ExampleAssert(actual);
    }

    @Override
    public Object getActual() {
        return actual;
    }
}
public class ExampleDTOAssert extends AbstractAssert<ExampleDTOAssert, ExampleDTO> implements HasValueAssert<ExampleDTOAssert> {

    private ExampleDTOAssert(ExampleDTO actual) {
        super(actual, ExampleDTOAssert.class);
    }

    public static ExampleDTOAssert assertThat(ExampleDTO actual) {
        return new ExampleDTOAssert(actual);
    }

    @Override
    public Object getActual() {
        return actual;
    }
}

如果允许修改业务类,可让Example和ExampleDTO实现一个统一的HasValue接口(包含getValue()方法),将接口中的getActual()改为返回HasValue类型,即可避免反射调用:

public interface HasValue {
    String getValue();
}

// 业务类实现该接口后,断言接口可优化为:
public interface HasValueAssert<SELF, T extends HasValue> {
    T getActual();

    default SELF hasValue(String expected) {
        Assertions.assertThat(getActual().getValue()).isEqualTo(expected);
        return (SELF) this;
    }
}

方案三:AssertJ Condition(无侵入式复用)

将通用断言逻辑封装为AssertJ的Condition,在匹配器中直接复用:

1. 定义通用Condition

import org.assertj.core.api.Condition;
import java.lang.reflect.Method;

public class ValueEqualsCondition extends Condition<Object> {
    private final String expectedValue;

    public ValueEqualsCondition(String expectedValue) {
        this.expectedValue = expectedValue;
        as("value equals '%s'", expectedValue);
    }

    @Override
    public boolean matches(Object actual) {
        try {
            Method getValueMethod = actual.getClass().getMethod("getValue");
            String actualValue = (String) getValueMethod.invoke(actual);
            return expectedValue.equals(actualValue);
        } catch (Exception e) {
            throw new AssertionError("Failed to extract value from object", e);
        }
    }
}

2. 在匹配器中使用Condition

public class ExampleAssert extends AbstractAssert<ExampleAssert, Example> {

    private ExampleAssert(Example actual) {
        super(actual, ExampleAssert.class);
    }

    public static ExampleAssert assertThat(Example actual) {
        return new ExampleAssert(actual);
    }

    public ExampleAssert hasValue(String expected) {
        matches(new ValueEqualsCondition(expected));
        return this;
    }
}
public class ExampleDTOAssert extends AbstractAssert<ExampleDTOAssert, ExampleDTO> {

    private ExampleDTOAssert(ExampleDTO actual) {
        super(actual, ExampleDTOAssert.class);
    }

    public static ExampleDTOAssert assertThat(ExampleDTO actual) {
        return new ExampleDTOAssert(actual);
    }

    public ExampleDTOAssert hasValue(String expected) {
        matches(new ValueEqualsCondition(expected));
        return this;
    }
}

该方案无需修改任何业务类或现有匹配器的结构,但使用了反射,适合无法修改原有代码的场景。


内容的提问来源于stack exchange,提问作者IceMajor

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 01:12:18