它有可能设计一种在编译时调用不同方法重载的方法吗?
可以说,我有这个小课程:
@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);
在我的“真实”情况下(我有一种非常特定的方式来更新对象,并且为每个对象提供了许多条件,并且存在复制和粘贴问题的机会非常高。因此,该向导强制所有开发人员以这种方式实施。):
我尝试了不同的方法。验证程序内部的复杂泛型,或使用泛型。我的上次批准是这样的。
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
-检查。
答案 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
)。我们对所有更新方法的要求(我们有许多数据库对象...)有点复杂。我们只需要一个更新方法(对于每个数据库对象),即...
使用许多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和用户名指向不同的类。
最后,我们可以非常流畅地使用更新方法:
userService.startUpdate()
.withId(currentUserId)
.setStatus(AccountStatus.INACTIVE)
.finallyUpdate();