Validasi data: kelas terpisah atau tidak?

16

Ketika saya memiliki banyak data yang perlu divalidasi, haruskah saya membuat kelas baru untuk tujuan validasi saja atau haruskah saya tetap menggunakan validasi dalam metode?

Contoh khusus saya merenungkan turnamen dan kelas acara / kategori: Tournamentdan Event, yang menjadi model turnamen olahraga dan setiap turnamen memiliki satu atau banyak kategori.

Ada semua hal yang perlu divalidasi dalam kelas-kelas ini: para pemain harus kosong, harus unik, jumlah pertandingan yang harus dimainkan setiap pemain, jumlah pemain yang dimiliki, pertandingan yang telah ditentukan, dan sebagainya yang sangat besar termasuk banyak lagi aturan yang rumit.

Ada juga beberapa bagian yang perlu saya validasi secara keseluruhan, seperti bagaimana kelas terintegrasi satu sama lain. Misalnya, validasi kesatuan aPlayer bisa baik-baik saja, tetapi jika suatu peristiwa memiliki pemain yang sama dua kali, itu adalah kesalahan validasi.

Jadi bagaimana dengan ini?

Jadi kita akan memiliki sesuatu seperti EventValidatordengan Eventsebagai anggota, dan validate()metode yang memvalidasi seluruh objek, ditambah metode tunggal untuk memvalidasi aturan semua anggota.

Kemudian, sebelum membuat instance objek yang valid, saya akan menjalankan validasi untuk mencegah nilai ilegal.

Apakah desain saya benar? Haruskah saya melakukan sesuatu yang berbeda?

Juga, haruskah saya menggunakan metode validasi pengembalian boolean? Atau hanya melempar pengecualian jika validasi gagal? Menurut saya pilihan terbaik adalah metode pengembalian boolean dan melemparkan pengecualian ketika objek instantiant, misalnya:

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?
    }
}
dabadaba
sumber

Jawaban:

7

Tidak masalah untuk mendelegasikan logika apa pun melalui komposisi jika logika itu akan berubah secara dinamis selama pelaksanaan suatu program. Validasi kompleks seperti yang Anda jelaskan adalah kandidat yang bagus untuk didelegasikan ke kelas lain melalui komposisi.

Ingatlah bahwa validasi dapat terjadi pada momen yang berbeda.

Instantiating validator konkret seperti dalam contoh Anda adalah ide yang buruk karena itu memasangkan kelas Event Anda ke validator tertentu.

Mari kita asumsikan Anda tidak menggunakan kerangka DI.

Anda dapat menambahkan validator ke konstruktor, atau menyuntikkannya dengan metode setter. Saya menyarankan metode pembuat di pabrik instantiates kedua Acara dan validator dan kemudian meneruskannya baik ke konstruktor acara atau dengan metode setValidator.

Jelas antarmuka Validator dan atau kelas abstrak harus ditulis sehingga kelas Anda bergantung padanya dan bukan pada validator konkret.

Menjalankan metode validasi dalam konstruktor dapat menjadi masalah karena semua negara yang ingin Anda validasi mungkin belum ada.

Saya menyarankan agar Anda membuat antarmuka yang Validable dan membiarkan kelas Anda mengimplementasikannya, antarmuka tersebut dapat memiliki metode validasi ().

Dengan cara itu komponen atas aplikasi Anda memanggil metode validasi sesuka hati (yang pada gilirannya didelegasikan ke anggota validator).

==> 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 Córdova
sumber
Jadi dalam hal hierarki kelas, ini tidak jauh berbeda dari yang saya sarankan, bukan? Alih-alih membuat dan memanggil validator di dalam konstruktor, itu akan dibuat dan dipanggil ketika diperlukan dalam aliran eksekusi, ini adalah perbedaan utama yang bisa saya lihat.
dabadaba
Jawaban saya pada dasarnya adalah tidak masalah untuk membuat kelas lain untuk melakukan validasi kompleks. Saya baru saja menambahkan beberapa saran bagi Anda untuk menghindari hard coupling dan mendapatkan lebih banyak fleksibilitas.
Tulains Córdova
Jika saya menambahkan pesan kesalahan dalam daftar atau dictioary, haruskah itu ada di dalam Validatoratau di dalam Validable? Dan bagaimana saya bisa mengerjakan pesan-pesan ini dalam hubungannya dengan ValidationException?
dabadaba
1
@dabadaba Daftar harus menjadi anggota implementator IValidator, tetapi IValidable harus menambahkan akses ke metode itu sehingga implementator mendelegasikannya. Saya berasumsi lebih dari satu pesan validasi dapat dikembalikan. Klik pada tautan yang diedit dan menit yang lalu di bagian bawah sehingga Anda dapat melihat perbedaannya. Lebih baik jika Anda menggunakan tampilan berdampingan (tanpa penurunan harga).
Tulains Córdova
3
Mungkin menjadi bertele-tele, tapi saya berasumsi Validatableadalah nama yang jauh lebih baik daripada Validablekata sifat yang sebenarnya
user919426
4

Saya lupa tentang semua pra-pemeriksaan saat menggunakan setter kelas model saya dan metode serupa untuk menambahkan data

Itulah masalahnya. Idealnya Anda harus mencegah objek Anda memiliki keadaan tidak valid: Jangan biarkan instantiasi dengan keadaan tidak valid dan jika Anda harus memiliki setter dan metode perubahan negara lainnya, lempar pengecualian di sana.

alih-alih saya membiarkan kelas validasi menangani validasi.

Ini bukan contradictionary. Jika logika validasi cukup kompleks untuk menjamin kelasnya sendiri, Anda masih dapat mendelegasikan validasi. Dan jika saya mengerti Anda benar, inilah yang sedang Anda lakukan sekarang:

Kemudian, sebelum membuat instance objek yang valid, saya akan menjalankan validasi untuk mencegah nilai ilegal.

Jika Anda masih memiliki setter yang mungkin menghasilkan keadaan tidak valid, pastikan untuk memvalidasi sebelum benar-benar mengubah keadaan. Kalau tidak, Anda akan memiliki pesan kesalahan tetapi masih meninggalkan objek Anda dengan keadaan tidak valid. Sekali lagi: mencegah objek Anda memiliki keadaan tidak valid

Juga, haruskah saya menggunakan metode validasi pengembalian boolean? Atau hanya melempar pengecualian jika validasi gagal? Menurut saya pilihan terbaik adalah metode pengembalian boolean dan melemparkan pengecualian ketika objek instantiant

Kedengarannya bagus bagi saya, karena validator mengharapkan input yang valid atau tidak valid sehingga dari perspektifnya, input yang tidak valid bukanlah pengecualian.

Pembaruan: Jika "sistem secara keseluruhan" menjadi tidak valid, alasannya adalah bahwa beberapa objek harus berubah (misalnya pemain) dan beberapa objek tingkat yang mungkin lebih tinggi (misalnya suatu peristiwa) yang merujuk objek ini memiliki kondisi yang tidak terpenuhi lagi . Sekarang solusinya adalah dengan tidak mengizinkan perubahan langsung ke pemain yang bisa membuat sesuatu menjadi tidak valid di level yang lebih tinggi. Di sinilah objek abadi membantu. Kemudian pemain yang diubah adalah objek yang berbeda. Setelah pemain dikaitkan dengan suatu peristiwa, Anda hanya dapat mengubahnya dari acara itu sendiri (karena Anda harus mengubah referensi ke objek baru) dan segera memvalidasi kembali.

Fabian Schmengler
sumber
Bagaimana dengan tidak memiliki cek apa pun di lembaga dan setter dan memiliki validasi sebagai operasi samping? Sesuatu seperti yang disarankan Tulains Córdova. Karena memvalidasi dan melempar pengecualian dalam setter atau konstruktor mungkin bekerja untuk anggota atau objek itu, tetapi tidak membantu memeriksa apakah sistem secara keseluruhan baik-baik saja.
dabadaba
@dabadaba jawaban saya terlalu panjang untuk komentar, silakan lihat pembaruan di atas
Fabian Schmengler
Saya tidak ingin menggunakan objek yang tidak dapat diubah, saya ingin dapat memodifikasi kelas saya. Dan saya tidak tahu cara membuat objek yang tidak dapat diubah, kecuali jika Anda merujuk pada kata kunci terakhir. Dalam hal ini saya harus banyak mengubah desain saya karena saya sebagian membangun acara dan turnamen dengan setter tambahan (karena data ini aditional dan dapat dikonfigurasi).
dabadaba
3
Untuk membuat objek yang tidak dapat diubah, hapus semua setter dan hanya gunakan konstruktor untuk menetapkan nilai. Jika Anda memiliki data opsional atau data yang tidak tersedia sekaligus, pertimbangkan untuk menggunakan pola pembangun: developer.amd.com/community/blog/2009/02/06/…
Fabian Schmengler