编译时的一般重载

时间:2019-02-20 14:46:26

标签: java

它有可能设计一种在编译时调用不同方法重载的方法吗?

可以说,我有这个小课程:

@RequiredArgsConstructor
public class BaseValidator<T> {
    private final T newValue;
}

现在,我需要返回不同对象的方法(取决于T)。 像这样:

private StringValidator getValidator() {
    return new ValidationString(newValue);
}

private IntegerValidator getValidator() {
    return new Validation(newValue);
}

最后,我想要一个非常流畅的呼叫层,看起来像这样:

new BaseValidator("string")
    .getValidator() // which returns now at compile-time a StringValidator
    .checkIsNotEmpty();
//or
new BaseValidator(43)
    .getValidator() // which returns now a IntegerValidator
    .checkIsBiggerThan(42);

在我的“真实”情况下(我有一种非常特定的方式来更新对象,并且为每个对象提供了许多条件,并且存在复制和粘贴问题的机会非常高。因此,该向导强制所有开发人员以这种方式实施。): Ide Image

我尝试了不同的方法。验证程序内部的复杂泛型,或使用泛型。我的上次批准是这样的。

public <C> C getValidator() {
    return (C) getValidation(newValue);
}

private ValidationString getValidation(String newValue) {
    return new StringValidator(newValue);
}

private ValidationInteger getValidation(Integer newValue) {
    return new IntegerValidation(newValue);
}

诀窍是什么?

// edit:我希望在编译时而不是在运行时进行instanceof-检查。

2 个答案:

答案 0 :(得分:4)

  

诀窍是什么?

不要这样做。

提供静态工厂方法:

class BaseValidator<T> {
  static ValidationString getValidation(String newValue) {
    return new ValidationString(newValue);
  }

  static ValidationInteger getValidation(Integer newValue) {
    return new ValidationInteger(newValue);
  }
}

class ValidationString extends BaseValidator<String> { ... }
class ValidationInteger extends BaseValidator<Integer> { ... }

尽管我认为这很奇怪:您指的是基类内部的子类。这种周期性的依赖关系使得代码难以使用,尤其是在重构时,甚至在初始化时。

相反,我建议创建一个包含工厂方法的实用程序类:

class Validators {
  private Validators() {}

  static ValidationString getValidation(String newValue) {
    return new ValidationString(newValue);
  }

  static ValidationInteger getValidation(Integer newValue) {
    return new ValidationInteger(newValue);
  }
}

没有这样的循环。


关于泛型,真正重要的一点是,它无非就是将显式强制转换隐式化(然后检查所有这些隐式强制转换是否是类型安全的)。

换句话说,这是

List<String> list = new ArrayList<>();
list.add("foo");
System.out.println(list.get(0).length());

只是一种更好的书写方式:

List list = new ArrayList();
list.add((String) "foo");
System.out.println(((String) list.get(0)).length());

尽管<String>看起来像是类型的一部分,但它基本上只是一条指令,指示编译器进行大量强制转换。

具有不同类型参数的泛型类都具有相同的方法。这是您的方法中的特定困难:您无法使BaseValidator<String>.getValidator()使用checkIsNotEmpty方法返回内容(仅),而BaseValidator<Integer>.getValidator()不能使用checkIsGreaterThan返回内容方法(仅)。

嗯,说你不能不是很正确。尝试使用方法范围的类型变量(<C> C getValidator())时,您可以编写:

new BaseValidator<>("string").<StringValidator>getValidator().checkIsNotEmpty()

(假设StringValidator上有checkIsNotEmpty方法)

但是:

  • 让我们不要碎话:这很丑。
  • 比丑陋更糟糕,它不是类型安全的。您可以同样写:

    new BaseValidator <>(“ string”)。getValidator()。checkIsGreaterThan(42)

    这是荒谬的,但编译器允许。问题是在调用站点上选择了返回类型:您要么必须返回null(并且在尝试调用以下方法时会得到NullPointerException);或返回一些非空值并冒ClassCastException的风险。两种方式:不好。

但是,您可以做的是使通用验证器成为方法调用的参数。例如:

interface Validator<T> {
  void validate(T b);
}

class BaseValidator<T> {
  BaseValidator<T> validate(Validator<T> v) {
    v.validate(this.value);
  }
}

并像这样调用,演示如何链接方法调用以应用多个验证:

new BaseValidator<>("")
    .validate(s -> !s.isEmpty())
    .validate(s -> s.matches("pattern"))
    ...

new BaseValidator<>(123)
    .validate(v -> v >= 0)
    ...

答案 1 :(得分:0)

我们决定添加更多的课堂步骤。您可以采用通用方式,也可以采用显式类型(在本示例中为String)。我们对所有更新方法的要求(我们有许多数据库对象...)有点复杂。我们只需要一个更新方法(对于每个数据库对象),即...

  1. 忽略为空的字段。
  2. 忽略等于“旧”值的字段。
  3. 验证不被忽略的字段。
  4. 仅在没有验证问题发生时保存。

使用许多if块来做到这一点是可能的,但是却不是很可读。复制粘贴失败的可能性很高。

我们的代码如下:

private void update(@NonNull final User.UpdateFinalStep params) {

    UpdateWizard.update(dbUserService.get(params.getId())

            .field(params.getStatus())
            .withGetter(DbUser::getAccountStatus)
            .withSetter(DbUser::setAccountStatus)
            .finishField()

            .field(Optional.ofNullable(params.getUsername())
               .map(String::toLowerCase)
               .orElse(null))
            .withGetter(DbUser::getUsername)
            .withSetter(DbUser::setUsername)
            .beginValidationOfField(FieldName.USERNAME)
            .notEmptyAndMatchPattern(USERNAME_PATTERN, () -> this.checkUniqueUsername(params.getUsername(), params.getId()))
            .endValidation()

            .field(params.getLastName())
            .withGetter(DbUser::getLastname)
            .withSetter(DbUser::setLastname)
            .beginValidationOfField(FieldName.USER_LASTNAME)
            .notEmptyAndMatchPattern(LAST_NAME_PATTERN)
            .endValidation()

            .field(params.getFirstName())
            .withGetter(DbUser::getFirstname)
            .withSetter(DbUser::setFirstname)
            .beginValidationOfField(FieldName.USER_FIRSTNAME)
            .notEmptyAndMatchPattern(FIRST_NAME_PATTERN)
            .endValidation()

            .save(dbUserService::save);
}

这是非常易读的,并允许以非常简单的方式添加新字段。使用泛型,我们不会给“愚蠢的开发人员”一个犯错的机会。

如您在图像中看到的,accountStatus和用户名指向不同的类。 Intellj Image

最后,我们可以非常流畅地使用更新方法:

userService.startUpdate()
   .withId(currentUserId)
   .setStatus(AccountStatus.INACTIVE)
   .finallyUpdate();