데이터 유효성 검사 : 분리 된 클래스?


16

검증해야 할 많은 데이터가있는 경우 검증 목적으로 만 새 클래스를 작성해야합니까? 아니면 메소드 내 검증을 사용해야합니까?

나의 특정 예는 토너먼트 및 이벤트 / 카테고리 클래스를 고려한다 : TournamentEvent, 스포츠 토너먼트를 모델링하고 각 토너먼트는 하나 이상의 카테고리를 갖는다.

이 클래스에는 검증해야 할 모든 것들이 있습니다 : 플레이어는 비어 있어야하고, 고유해야하며, 각 플레이어가 플레이해야하는 경기 수, 각 경기가 보유한 플레이어 수, 미리 정의 된 매치업 및 훨씬 더 큰 등을 포함해야합니다. 복잡한 규칙.

클래스가 서로 통합되는 방식과 같이 전체적으로 유효성을 검사해야하는 부분도 있습니다. 예를 들어 a의 단일 유효성 검사는 Player괜찮을 수 있지만 이벤트에 동일한 플레이어가 두 번 있으면 유효성 검사 오류입니다.

어떻습니까? : 모델 클래스의 setter 및 유사한 메소드를 사용하여 데이터를 추가 할 때 사전 검사를 잊어 버리는 대신 유효성 검사 클래스가 처리하도록합니다.

그래서 우리는 같은 것 EventValidatorEvent구성원으로하고, validate()모든 구성원의 규칙을 확인하기 위해 전체 개체의 유효성을 확인하는 방법, 플러스 유일한 방법입니다.

그런 다음 유효한 개체를 인스턴스화하기 전에 잘못된 값을 방지하기 위해 유효성 검사를 실행합니다.

내 디자인이 맞습니까? 다르게 행동해야합니까?

또한 부울 리턴 유효성 검사 방법을 사용해야합니까? 또는 유효성 검사가 실패하면 예외를 throw합니까? 가장 좋은 옵션은 부울 반환 메소드이며 객체가 인스턴스화 될 때 예외를 throw하는 것 같습니다.

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 메서드로 주입 할 수 있습니다. 팩토리의 작성자 메소드는 Event와 validator를 인스턴스화 한 다음 이벤트 생성자 또는 setValidator 메소드로 전달하는 것이 좋습니다.

분명히 유효성 검사기 인터페이스 및 / 또는 추상 클래스를 작성하여 클래스가 구체적인 유효성 검사기가 아닌 인터페이스에 의존해야합니다.

검증하려는 모든 상태가 아직 제자리에 있지 않을 수 있으므로 생성자에서 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);
            }
        }
    }
}

따라서 클래스 계층 구조 측면에서 이것은 내가 제안한 것과 크게 다르지 않습니까? 생성자 내에서 유효성 검사기를 인스턴스화하고 호출하는 대신 실행 흐름에서 필요할 때 생성 및 호출됩니다. 이것은 내가 볼 수있는 주요 차이점입니다.
dabadaba

내 대답은 기본적으로 복잡한 유효성 검사를 수행하기 위해 다른 클래스를 만드는 것이 좋습니다. 하드 커플 링을 피하고 유연성을 높이기 위해 몇 가지 조언을 추가했습니다.
Tulains Córdova

나는 목록 또는 dictioary에서 오류 메시지를 추가한다면, 그것은에 있어야 Validator또는에서 Validable? 그리고이 메시지를 ValidationException어떻게?
dabadaba

1
@dabadaba이 목록은 IValidator 구현 자의 구성원이어야하지만 IValidable은 해당 메소드에 대한 액세스 권한을 추가하여 구현자가 위임해야합니다. 둘 이상의 유효성 검사 메시지가 반환 될 수 있다고 가정했습니다. 온 카릭 전에 편집 N 분 하단에 링크하면이 차이를 볼 수 있습니다. 나란히 보기 (마크 다운 없음) 를 사용하는 것이 좋습니다 .
Tulains Córdova

3
아마도 pedantic ValidatableValidable
일지

4

모델 클래스의 setter 및 유사한 메소드를 사용하여 데이터를 추가 할 때 사전 검사를 잊어 버립니다.

그게 문제입니다. 이상적으로 객체가 유효하지 않은 상태가되지 않도록해야합니다.

대신 유효성 검사 클래스가 유효성 검사를 처리하도록했습니다.

이것은 모순되지 않습니다. 유효성 검사 논리가 자체 클래스를 보증하기에 충분히 복잡한 경우에도 유효성 검사를 위임 할 수 있습니다. 그리고 내가 당신을 옳게 이해한다면, 이것은 당신이 지금하고있는 일입니다.

그런 다음 유효한 개체를 인스턴스화하기 전에 잘못된 값을 방지하기 위해 유효성 검사를 실행합니다.

여전히 유효하지 않은 상태가 될 수있는 세터가있는 경우 실제로 상태를 변경 하기 전에 유효성을 검증하십시오 . 그렇지 않으면 오류 메시지가 표시되지만 여전히 개체를 유효하지 않은 상태로 둡니다. 다시 : 객체가 유효하지 않은 상태가되지 않도록하십시오 .

또한 부울 리턴 유효성 검사 방법을 사용해야합니까? 또는 유효성 검사가 실패하면 예외를 throw합니까? 가장 좋은 옵션은 부울 반환 메소드이며 객체가 인스턴스화 될 때 예외를 throw하는 것 같습니다.

유효성 검사기가 유효하거나 유효하지 않은 입력을 기대하므로 유효하지 않은 입력은 예외가 아닙니다.

업데이트 : "시스템 전체"가 유효하지 않은 경우, 일부 오브젝트 (예 : 플레이어)가 변경되어야하고이 오브젝트를 참조하는 상위 레벨 오브젝트 (예 : 이벤트)가 더 이상 충족되지 않는 조건이 있기 때문입니다. . 이제 해결책은 플레이어를 직접 변경하여 더 높은 수준에서 무언가를 무효화 할 수 없도록하는 것입니다. 불변의 객체가 도움 이되는 곳 입니다. 그런 다음 변경된 플레이어는 다른 개체입니다. 플레이어가 이벤트에 연결되면 이벤트 자체에서만 이벤트를 변경하고 (새 개체에 대한 참조를 변경해야하기 때문에) 즉시 다시 확인할 수 있습니다.


인스턴스화 및 세터에서 검사를 수행하지 않고 별도의 작업으로 유효성 검사를 수행하는 것은 어떻습니까? Tulains Córdova와 같은 것이 제안했습니다. setter 또는 생성자에서 예외의 유효성을 검사하고 throw하면 해당 멤버 또는 객체에 대해 작동 할 수 있지만 시스템 전체가 정상인지 확인하는 데 도움이되지 않습니다.
dabadaba

@dabadaba 내 대답은 코멘트를 그리워하는 것이었다, 위의 업데이트를 참조하십시오
Fabian Schmengler

그래도 불변 객체를 사용하고 싶지 않고 클래스를 수정할 수 있기를 원합니다. 그리고 최종 키워드를 언급하지 않는 한 불변의 객체를 만드는 방법을 모르겠습니다. 이 경우 추가 세터가있는 이벤트와 토너먼트를 부분적으로 구성하기 때문에 디자인을 많이 변경해야합니다 (이 데이터는 추가적이고 구성 가능하기 때문입니다).
dabadaba

3
변경 불가능한 객체를 만들려면 모든 세터를 제거하고 생성자를 사용하여 값을 설정하십시오. 옵션 데이터 나 한 번에 사용할 수없는 데이터가있는 경우 빌더 패턴 사용을 고려하십시오. developer.amd.com/community/blog/2009/02/06/…
Fabian Schmengler
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.