Xác thực dữ liệu: tách lớp hay không?


15

Khi tôi có nhiều dữ liệu cần được xác thực, tôi có nên tạo một lớp mới cho mục đích xác thực duy nhất hay tôi nên gắn bó với xác thực trong phương thức?

Ví dụ cụ thể của tôi dự tính một giải đấu và một lớp sự kiện / thể loại: TournamentEvent, mô hình một giải đấu thể thao và mỗi giải đấu có một hoặc nhiều thể loại.

Có tất cả mọi thứ để xác nhận trong các lớp này: người chơi nên trống, nên là duy nhất, số lượng trận đấu mà mỗi người chơi nên chơi, số lượng người chơi mỗi trận đấu, trận đấu được xác định trước và một vvetera thực sự lớn bao gồm nhiều hơn nữa quy tắc phức tạp.

Ngoài ra còn có một số phần tôi cần xác thực toàn bộ, như cách các lớp tích hợp với nhau. Ví dụ: xác thực đơn nhất của một Playercó thể chỉ tốt, nhưng nếu một sự kiện có cùng một người chơi hai lần, đó là lỗi xác thực.

Vậy làm thế nào về điều này?: Tôi quên hoàn toàn mọi kiểm tra trước khi sử dụng setters của các lớp mô hình và các phương thức tương tự để thêm dữ liệu và thay vào đó tôi để các lớp xác nhận xử lý việc đó.

Vì vậy, chúng ta sẽ có một cái gì đó giống như EventValidatorvới Eventtư cách là một thành viên và một validate()phương thức xác nhận toàn bộ đối tượng, cộng với các phương thức số ít để xác thực các quy tắc của tất cả các thành viên.

Sau đó, trước khi khởi tạo một đối tượng hợp lệ, tôi sẽ thực hiện xác nhận để ngăn chặn các giá trị bất hợp pháp.

Thiết kế của tôi có đúng không? Tôi có nên làm một cái gì đó khác nhau?

Ngoài ra, tôi có nên sử dụng các phương thức xác nhận trả về boolean không? Hoặc chỉ cần ném một ngoại lệ nếu xác nhận thất bại? Đối với tôi, dường như tùy chọn tốt nhất sẽ là các phương thức trả về boolean và ném ngoại lệ khi đối tượng được khởi tạo, ví dụ:

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

Câu trả lời:


7

Bạn có thể ủy quyền bất kỳ logic nào bằng cách sáng tác nếu logic đó sẽ thay đổi linh hoạt trong quá trình thực hiện chương trình. Các xác nhận phức tạp như những gì bạn giải thích là một ứng cử viên tốt như bất kỳ ai được ủy quyền cho một lớp khác thông qua thành phần.

Hãy nhớ rằng mặc dù xác nhận có thể xảy ra trong những thời điểm khác nhau.

Khởi tạo một trình xác nhận cụ thể như trong ví dụ của bạn là một ý tưởng tồi bởi vì nó kết hợp lớp Sự kiện của bạn với trình xác nhận cụ thể đó.

Giả sử bạn không sử dụng bất kỳ khung DI nào.

Bạn có thể thêm trình xác nhận vào hàm tạo hoặc thêm nó bằng phương thức setter. Tôi đề xuất một phương thức của người tạo trong một nhà máy khởi tạo cả Sự kiện và trình xác nhận và sau đó chuyển nó cho hàm tạo sự kiện hoặc bằng phương thức setValidator.

Rõ ràng một giao diện Trình xác thực và hoặc lớp trừu tượng nên được viết để các lớp của bạn phụ thuộc vào nó chứ không phụ thuộc vào bất kỳ trình xác nhận cụ thể nào.

Việc thực thi phương thức xác thực trong hàm tạo có thể có vấn đề vì tất cả trạng thái bạn muốn xác thực có thể chưa được thực hiện.

Tôi đề nghị bạn nên tạo một giao diện hợp lệ và để các lớp của bạn thực hiện nó, giao diện đó có thể có một phương thức xác thực ().

Bằng cách đó, các thành phần trên của ứng dụng của bạn gọi phương thức xác thực theo ý muốn (lần lượt được ủy quyền cho thành viên trình xác nhận).

==> 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;
    }

}

==> Xác thựcException.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);
            }
        }
    }
}

Vì vậy, về mặt phân cấp lớp, điều này không khác nhiều so với những gì tôi đề xuất, phải không? Thay vì khởi tạo và gọi trình xác nhận bên trong hàm tạo, nó sẽ được tạo và gọi khi cần trong luồng thực thi, đây là điểm khác biệt chính tôi có thể thấy.
dabadaba

Câu trả lời của tôi về cơ bản là ổn khi tạo một lớp khác để thực hiện xác nhận phức tạp. Tôi chỉ thêm một số lời khuyên cho bạn để tránh khớp nối cứng và linh hoạt hơn.
Tulains Córdova

Nếu tôi thêm các thông báo lỗi trong một danh sách hoặc độc tài, thì nó nên ở trong Validatorhay trong Validable? Và làm thế nào tôi có thể làm việc những tin nhắn này kết hợp với ValidationException?
dabadaba

1
@dabadaba Danh sách nên là thành viên của những người triển khai IValidator, nhưng IValidable nên thêm quyền truy cập vào phương thức đó để những người triển khai ủy thác nó. Tôi giả sử nhiều hơn một tin nhắn xác nhận có thể được trả lại. Clic trên liên kết được chỉnh sửa n phút trước ở phía dưới để bạn có thể thấy sự khác biệt. Sẽ tốt hơn nếu bạn sử dụng chế độ xem cạnh nhau (không đánh dấu).
Tulains Córdova

3
Có lẽ là mang tính mô phạm, nhưng tôi cho rằng đó Validatablelà một cái tên tốt hơn nhiều so Validablevới nó là tính từ thực sự
user919426

4

Tôi quên hoàn toàn mọi kiểm tra trước khi sử dụng setters của các lớp mô hình và các phương thức tương tự để thêm dữ liệu

Đó chính là vấn đề. Tốt nhất là bạn nên ngăn các đối tượng của mình có trạng thái không hợp lệ: Không cho phép khởi tạo với trạng thái không hợp lệ và nếu bạn phải có setters và các phương thức thay đổi trạng thái khác, hãy ném ngoại lệ ngay tại đó.

thay vào đó tôi để các lớp xác nhận xử lý xác nhận.

Đây không phải là mâu thuẫn. Nếu logic xác thực đủ phức tạp để đảm bảo lớp của chính nó, bạn vẫn có thể ủy quyền xác thực. Và nếu tôi hiểu bạn đúng, đây chính xác là những gì bạn đang làm bây giờ:

Sau đó, trước khi khởi tạo một đối tượng hợp lệ, tôi sẽ thực hiện xác nhận để ngăn chặn các giá trị bất hợp pháp.

Nếu bạn vẫn có setters có thể dẫn đến trạng thái không hợp lệ, hãy đảm bảo xác nhận trước khi thực sự thay đổi trạng thái. Nếu không, bạn sẽ có một thông báo lỗi nhưng vẫn để đối tượng của bạn ở trạng thái không hợp lệ. Một lần nữa: ngăn chặn các đối tượng của bạn có trạng thái không hợp lệ

Ngoài ra, tôi có nên sử dụng các phương thức xác nhận trả về boolean không? Hoặc chỉ cần ném một ngoại lệ nếu xác nhận thất bại? Dường như với tôi, tùy chọn tốt nhất sẽ là các phương thức trả về boolean và ném ngoại lệ khi đối tượng được khởi tạo

Nghe có vẻ tốt với tôi, vì trình xác nhận kỳ vọng đầu vào hợp lệ hoặc không hợp lệ vì vậy theo quan điểm của nó, đầu vào không hợp lệ không phải là một ngoại lệ.

Cập nhật: Nếu "toàn bộ hệ thống" trở nên không hợp lệ, lý do là một số đối tượng phải thay đổi (ví dụ: trình phát) và một số đối tượng cấp cao hơn (ví dụ: một sự kiện) tham chiếu đến đối tượng này có một điều kiện không được đáp ứng nữa . Bây giờ một giải pháp sẽ là không cho phép thay đổi trực tiếp đến người chơi có thể khiến thứ gì đó không hợp lệ ở cấp cao hơn. Đây là nơi mà các đối tượng bất biến giúp đỡ. Sau đó, một người chơi thay đổi là một đối tượng khác nhau. Khi một người chơi được liên kết với một sự kiện, bạn chỉ có thể thay đổi nó từ chính sự kiện đó (vì bạn sẽ phải thay đổi tham chiếu sang một đối tượng mới) và xác nhận lại ngay lập tức.


Điều gì về việc không có kiểm tra bất cứ điều gì trong khởi tạo và setters và có xác nhận là một hoạt động sang một bên? Một cái gì đó như Tulains Córdova đề xuất. Bởi vì việc xác nhận và ném một ngoại lệ trong setter hoặc constructor có thể hoạt động cho thành viên hoặc đối tượng đó, nhưng nó không giúp kiểm tra xem toàn bộ hệ thống có ổn không.
dabadaba

@dabadaba câu trả lời của tôi là mong muốn nhận xét, vui lòng xem cập nhật ở trên
Fabian Schmengler

Tôi không muốn sử dụng các đối tượng bất biến, tôi muốn có thể sửa đổi các lớp học của mình. Và tôi không biết làm thế nào để tạo ra các đối tượng bất biến, trừ khi bạn đang đề cập đến từ khóa cuối cùng. Trong trường hợp đó, tôi sẽ phải thay đổi thiết kế của mình rất nhiều vì tôi đang xây dựng một phần các sự kiện và giải đấu với các setters bổ sung (vì dữ liệu này là quảng cáo và có thể định cấu hình).
dabadaba

3
Để tạo các đối tượng bất biến, loại bỏ tất cả các setters và chỉ sử dụng các hàm tạo để đặt giá trị. Nếu bạn có dữ liệu tùy chọn hoặc dữ liệu không có sẵn ngay lập tức, hãy xem xét sử dụng mẫu trình tạo: developer.amd.com/community/blog/2009/02/06/ trên
Fabian Schmengler
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.