Java:方法/构造函数参数验证。还应该有多个验证调用吗?

时间:2017-12-21 08:10:13

标签: java validation methods

我有一个关于验证方法/构造函数参数的问题。

我们说我有一个班级BankAccount。这个类在构造函数中执行一些基本的验证(注意:这不是我如何实现这样一个类,最好使用多态和接口实现来提供不同类型的银行账户。这里是一个枚举BankAccount.Type使用)。

BankAccount(Type type, double balance, String ownerName, String accountNumber) {
    // Make sure no invalid arguments are passed.
    if (type == null) {
        throw new IllegalArgumentException("'type' may not be null!");
    }
    if (balance < type.getBalanceLimit()) {
        throw new IllegalArgumentException("initial 'balance' may not be less than the accounts balance limit!");
    }
    if (ownerName == null || ownerName.trim().isEmpty()) {
        throw new IllegalArgumentException("'ownerName' may not be null or empty!");
    }
    if (!type.requiresOwner()) {
        throw new IllegalStateException("Cannot create an account with an owner if that account type does not require an owner!");
    }
    if (accountNumber == null || accountNumber.trim().isEmpty()) {
        throw new IllegalArgumentException("'accountNumber' may not be null or empty!");
    }

    this.type = type;
    this.balance = balance;
    this.ownerName = ownerName;
    this.accountNumber = accountNumber;
}

首先:这是验证构造函数参数的方法,还是首先设置所有字段然后调用方法validateState()来检查构造状态是否有效否则抛出异常。像这样:

BankAccount(Type type, double balance, String ownerName, String accountNumber) {
        this.type = type;
        this.balance = balance;
        this.ownerName = ownerName;
        this.accountNumber = accountNumber;

        validateState();
    } 

上述哪种解决方案更受欢迎(在大多数情况下)?

我的问题的第二部分是关于重复验证。假设我有一个班级Bank。这个类有一个方法createGiroAccount(Account.Type type, String ownerName)

/**
         * Creates a new {@link Account} of the specified {@link Account.Type} for the given owner.
         * Note that the {@link Account.Type} must be a giro account type.
         *
         * @param type      the type of the new account which must be one of the available giro options, not null
         * @param ownerName the owner´s name who owns the new account, not null or empty
         */
        public void createGiroAccount(BankAccount.Type type, String ownerName) {
            // Really basic validation
            if (type == null || !type.isGiro()) {
                throw new IllegalArgumentException("'type' may not be null and must be of type giro!");
            }
            if (ownerName == null || ownerName.trim().isEmpty()) {
                throw new IllegalArgumentException("'ownerName' may not be null or empty");
            }

            String accountNumber = generateUniqueAccountNumber();
            accounts.add(new BankAccount(type, ownerName, accountNumber));
        }

我的问题:即使BankAccount验证了参数/状态,是否也应该验证参数?

3 个答案:

答案 0 :(得分:1)

  

这是验证构造函数参数时的方法,还是首先设置所有字段然后调用方法validateState()

如果您不想编写重复代码,可以创建custom annotation并检查参数的有效性。

  

我的问题的第二部分是关于重复验证。

如果您只有一个构造函数,则createGiroAccount中不需要检查。否则你会这样做。

答案 1 :(得分:1)

在构造函数或专用方法中进行验证?

两种解决方案都没问题。

诀窍在于,如果在构造函数中添加大量验证代码,并与适当的构造函数代码混合,则可能难以维护。

顺便说一句,如果你选择使用专用方法validateState();,你应该在对构造函数做任何事情之前调用它,它对处理代码没用,后来发现你的一个参数无效并抛出异常......

如果您想进行干净验证,可以使用javax.validation包。见this

然后,您可以创建正确的注释,并注释构造函数参数。

BankAccount(Type type, @ValidBalance double balance, @NotEmpty String ownerName, @NotEmpty String accountNumber) {

@ValidBalance将成为自定义验证。

答案 2 :(得分:1)

很多方法可以做到,而且没有一个“正确”。在现实生活中的系统中,你会遇到像DB连接框架和REST API这样的东西,它们假设你将使用构造函数中根本没有任何参数的类。

所以你可能拥有的interface Account无论如何都无法进行任何验证。

interface BackAccount {
    AccountType getAccountType();
    BigDecimal getBalance();
    Person getOwner();
}

请注意,所有者与String的班级不同;这允许假设如果您有BankAccount,则无需验证所有者,因为在创建该对象时会发生Person验证。你可能有一个类似

的验证框架
interface Validator<T> {
    boolean isValid(T toValidate);
}

这将允许您为Persons,BankAccounts等实施验证。在您的情况下,您有一个

class TypeBalanceValidator<BankAccount> implements Validator<BankAccount> {
    public boolean isValid(BankAccount account) {
        return account.getBalance().compareTo(getMinBalanceForType(account.getType())) > 0;
    }
}

至于创建BankAccount,除了自动从数据库中读取外,你可能有一个Service,提供了一种工厂方法,与你的方法不同,但是有类似

的东西。
interface AccountService {
    BankAccount createGiroAccount(Person p);
}

为什么你甚至会将类型传递给只允许AccountType.GIRO的方法?无论如何,这是您创建BankAccount的地方以及进行验证的地方。