web-dev-qa-db-ja.com

データ検証:分離されたクラスかどうか?

検証が必要なデータが大量にある場合、検証のみを目的として新しいクラスを作成する必要がありますか、それともメソッド内検証を使用する必要がありますか?

私の特定の例では、トーナメントとイベント/カテゴリクラス、TournamentEventを想定しています。これらはスポーツトーナメントをモデル化し、各トーナメントには1つ以上のカテゴリがあります。

これらのクラスで検証する必要のあるものはすべてあります。プレーヤーは空である必要があり、一意である必要があり、各プレーヤーがプレイする必要のあるマッチの数、各マッチのプレーヤーの数、事前定義されたマッチアップ、およびその他多くのことを含みます複雑なルール。

クラスを相互に統合する方法など、全体として検証する必要があるいくつかの部分もあります。たとえば、Playerの単一検証は問題ありませんが、イベントに同じプレーヤーが2回ある場合、検証エラーになります。

では、これについてはどうですか?:モデルクラスのセッターや同様のメソッドを使用してデータを追加する場合、事前チェックをまったく忘れ、代わりに検証クラスで処理します。

したがって、EventValidatorのようなものをメンバーとしてEventとともに、オブジェクト全体を検証するvalidate()メソッドと、すべてのメンバーのルールを検証する単一のメソッドを用意します。

次に、有効なオブジェクトをインスタンス化する前に、無効な値を防ぐために検証を実行します。

私のデザインは正しいですか?別のことをする必要がありますか?

また、ブール値を返す検証メソッドを使用する必要がありますか?または、検証が失敗した場合に例外をスローしますか?私にとって最良のオプションは、ブール値を返すメソッドであり、オブジェクトがインスタンス化されたときに例外をスローすることです。次に例を示します。

public Event() {
    EventValidator eventValidator = new EventValidator(this);
    if (!eventValidator.validate()) {
        // show error messages with methods defined in the validator
        throw new Exception(); // what type of exception would be best? should I create custom ones?
    }
}
17
dabadaba

構成によってロジックを委任しても問題ありませんプログラムの実行中にそのロジックが動的に変更される場合。あなたが説明するような複雑な検証は、コンポジションを介して別のクラスに委任されるのと同じくらい良い候補です。

ただし、検証はさまざまなタイミングで発生する可能性があることに注意してください。

例のように具象バリデーターをインスタンス化することは、Eventクラスをその特定のバリデーターに結合するため、お勧めできません。

DIフレームワークを使用していないとします。

バリデーターをコンストラクターに追加するか、セッターメソッドを挿入できます。ファクトリーのクリエーターメソッドは、イベントとバリデーターの両方をインスタンス化し、それをイベントコンストラクターに渡すか、setValidatorメソッドで渡すことをお勧めします。

明らかに、Validatorインターフェイスや抽象クラスを記述して、クラスが具体的なバリデーターではなく、それに依存するようにする必要があります。

検証するすべての状態がまだ整っていない可能性があるため、コンストラクターでvalidateメソッドを実行すると問題が発生する可能性があります。

Validableインターフェースを作成してクラスに実装させることをお勧めします。そのインターフェースにはvalidate()メソッドを含めることができます。

このようにして、アプリケーションの上部コンポーネントは、validateメソッドを自由に呼び出します(これはバリデーターメンバーに委譲されます)。

==> IValidable.Java <==

import Java.util.List;

public interface IValidable {
    public void setValidator(IValidator<Event> validator_);
    public void validate() throws ValidationException;
    public List<String> getMessages();
}

==> IValidator.Java <==

import Java.util.List;

public interface IValidator<T> {
    public boolean validate(T e);
    public List<String> getValidationMessages();
}

==> Event.Java <==

import Java.util.List;

public class Event implements IValidable {

    private IValidator<Event> validator;

    @Override
    public void setValidator(IValidator<Event> validator_) {
        this.validator = validator_;
    }

    @Override
    public void validate() throws ValidationException {
        if (!this.validator.validate(this)){
            throw new ValidationException("WTF!");
        }
    }

    @Override
    public List<String> getMessages() {
        return this.validator.getValidationMessages();
    }

}

==> SimpleEventValidator.Java <==

import Java.util.ArrayList;
import Java.util.List;

public class SimpleEventValidator implements IValidator<Event> {

    private List<String> messages = new ArrayList<String>();
    @Override
    public boolean validate(Event e) {
        // do validations here, by accessing the public getters of e
        // propulate list of messages is necessary
        // this example always returns false    
        return false;
    }

    @Override
    public List<String> getValidationMessages() {
        return this.messages;
    }

}

==> ValidationException.Java <==

public class ValidationException extends Exception {
    public ValidationException(String message) {
        super(message);
    }

    private static final long serialVersionUID = 1L;
}

==> Test.Java <==

public class Test {
    public static void main (String args[]){
        Event e = new Event();
        IValidator<Event> v = new SimpleEventValidator();
        e.setValidator(v);
        // set other thins to e like
        // e.setPlayers(player1,player2,player3)
        // e.setNumberOfMatches(3);
        // etc
        try {
            e.validate();
        } catch (ValidationException e1) {
            System.out.println("Your event doesn't comply with the federation regulations for the following reasons: ");
            for (String s: e.getMessages()){
                System.out.println(s);
            }
        }
    }
}
7

モデルクラスのセッターや同様のメソッドを使用してデータを追加する場合、事前チェックをまったく忘れます

それが問題です。理想的には、オブジェクトが無効な状態になるのを防ぐ必要があります。無効な状態でのインスタンス化を許可しないでください。セッターやその他の状態変更メソッドが必要な場合は、すぐに例外をスローしてください。

代わりに、検証クラスに検証を処理させます。

これは矛盾しない。検証ロジックが独自のクラスを保証するほど複雑な場合でも、検証を委任できます。そして、私があなたが正しいと理解している場合、これはまさにあなたが今やっていることです:

次に、有効なオブジェクトをインスタンス化する前に、無効な値を防ぐために検証を実行します。

無効な状態になる可能性のあるセッターがまだある場合は、beforeが実際に変化する状態を検証してください。そうしないと、エラーメッセージが表示されますが、オブジェクトは無効な状態のままになります。もう一度:オブジェクトが無効な状態になるのを防ぐ

また、ブール値を返す検証メソッドを使用する必要がありますか?または、検証が失敗した場合に例外をスローしますか?私にとって最良のオプションは、ブール値を返すメソッドであり、オブジェクトがインスタンス化されたときに例外をスローすることです

バリデーターは有効または無効な入力を期待するので、その観点からは無効な入力も例外ではありません。

pdate:「システム全体」が無効になった場合、その理由は、一部のオブジェクト(プレーヤーなど)が変更されている必要があり、このオブジェクトを参照するより高いレベルのオブジェクト(イベントなど)がもう満たされていない状態。これで解決策は、上位レベルで何かが無効になる可能性のあるプレーヤーへの直接変更を許可しないことです。これが不変オブジェクトヘルプの場所です。次に、変更されたプレーヤーは別のオブジェクトです。プレーヤーをイベントに関連付けると、イベント自体からしか変更できず(新しいオブジェクトへの参照を変更する必要があるため)、すぐに再度検証できます。

4