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


15

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

私の特定の例では、トーナメントやイベント/カテゴリクラス企図:TournamentEvent、モデルのスポーツ大会や各トーナメントは、1つのまたは多数のカテゴリがあります。

これらのクラスで検証するすべての種類のものがあります:プレイヤーは空である必要があり、一意である必要があり、各プレイヤーがプレイするマッチの数、各マッチが持っているプレイヤーの数、事前に定義されたマッチアップ、およびはるかに大きいなど複雑なルール。

また、クラスを相互に統合する方法など、全体として検証する必要のある部分もあります。たとえば、aのユニタリ検証は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?
    }
}

回答:


7

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

ただし、検証は異なる瞬間に発生する可能性があることを忘れないでください。

あなたの例のように具体的なバリデータをインスタンス化することは、Eventクラスをその特定のバリデータに結合するため、悪いアイデアです。

DIフレームワークを使用していないと仮定しましょう。

コンストラクターにバリデーターを追加するか、setterメソッドを使用して注入できます。ファクトリー内のクリエーターメソッドは、イベントとバリデーターの両方をインスタンス化し、イベントコンストラクターまたは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);
            }
        }
    }
}

それで、クラス階層に関しては、これは私が提案したものと大差ないでしょうか?コンストラクター内でバリデーターをインスタンス化して呼び出す代わりに、実行フローで必要なときに作成されて呼び出されます。これは私が見ることができる主な違いです。
ダバダバ

私の答えは、基本的に、複雑な検証を行うために別のクラスを作成しても構いません。ハードカップリングを回避し、柔軟性を高めるためのアドバイスを追加しました。
Tulainsコルドバ

リストまたは辞書にエラーメッセージを追加する場合、それは中にあるべきですか、Validatorそれとも中にあるべきValidableですか?そして、これらのメッセージをどのように連携させることができますValidationExceptionか?
ダバダバ

1
@dabadabaリストはIValidator実装者のメンバーである必要がありますが、IValidableはそのメソッドへのアクセスを追加して実装者がそれを委任する必要があります。複数の検証メッセージが返されると想定しました。下部の編集されたn分前のリンクをクリックして、違いを確認します。並べて表示する場合(マークダウンなし)をお勧めします。
Tulainsコルドバ

3
おそらくValidatableValidable
つまらない

4

モデルクラスのセッターや同様のメソッドを使用してデータを追加する際の事前チェックは絶対に忘れます

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

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

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

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

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

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

バリデーターは有効な入力または無効な入力を期待しているので、私にとっては良さそうです。そのため、無効な入力は例外ではありません。

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


インスタンス化とセッターでチェックを一切行わず、検証を別の操作として行うのはどうですか?TulainsCórdovaのようなものが提案しました。セッターまたはコンストラクターで例外を検証およびスローすることは、そのメンバーまたはオブジェクトに対して機能する可能性がありますが、システム全体が正常であるかどうかを確認する助けにはなりません。
ダバダバ

@dabadaba私の答えはコメントを待ち望んでいた、上記の更新を参照してください
ファビアンシュメングラー

ただし、不変オブジェクトを使用したくないので、クラスを変更できるようにします。そして、あなたが最終キーワードを参照していない限り、私は不変オブジェクトを作成する方法を知りません。その場合、追加のセッターを使用してイベントやトーナメントを部分的に構築しているため、デザインを大幅に変更する必要があります(このデータは追加で構成可能だからです)。
ダバダバ

3
不変オブジェクトを作成するには、すべてのセッターを削除し、コンストラクターのみを使用して値を設定します。あなたが一度に利用できないオプションのデータやデータを持っている場合は、Builderパターンを使用することを検討してください:developer.amd.com/community/blog/2009/02/06/...
ファビアンSchmengler
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.