Programing/Design Patterns

Javascript 전략 패턴(strategy pattern), 데이터 유효성 검증

리커니 2022. 3. 30. 13:55
반응형

Javascript 전략 패턴(strategy pattern), 데이터 유효성 검증

 

런타임에 알고리즘을 선택할 수 있는 전략 패턴에 대해 알아보도록 하겠습니다.

전략패턴은 인터페이스를 유지하면서 특정한 작업을 해야할 때 원하는 알고리즘을

상황에 맞게 선택할 수 있습니다.

 

데이터의 유효성을 검사하는 Validator Class를 전략패턴을 활용하여 구현해보도록 하겠습니다.

즉시 실행 함수 내 클래스를 작성하고 window.AVT로 접근 가능하게 구현합니다.

 

(function(){
    class Validator{
        constructor(){
        }
    }
    window.AVT = new Validator();
})();

 

이제 클래스 변수로

유효성 검증 에러 메시지를 출력할 msg,

유효성 검증 로직이 들어갈  types,

유효성 검증 설정값인 config를 생성합니다.

 

(function(){
    class Validator{
        constructor(){
            this.msg = [];
            this.types = {};
            this.conig = {};
        }
    }
    window.AVT = new Validator();
})();

 

이제 동적으로 원하는 유효성 검증 로직을 타게하기 위해 setTypes 메소드를 추가하고 실행하게 구성합니다. 

setTypes 메소드 내에는 types에 유효성 검증 로직을 구현해줍니다.

각 types 객체 내 validate 메소드를 화살표 함수와 익명함수로 다르게 구현한 이유는 this 바인딩 때문입니다.

일반적으로 객체 내 this는 자기자신, 내부함수의 this는 window, 화살표 함수의 this는 상위 스코프의 this 입니다.

 

Link : https://aljjabaegi.tistory.com/288

 

javascript this 알짜만 빼먹기! this 마스터!

javascript this 알짜만 빼먹기! this 마스터! Javascript로 개발을 할 때 this는 정말 많이 활용됩니다. 각 상황별 this에 바인딩 되는 객체를 잘못 파악한다면 원하지 않는 결과를 가져올 수 있습니다.

aljjabaegi.tistory.com

 

(function(){
    class Validator{
        constructor(){
            this.msg = [];
            this.types = {};
            this.conig = {};
            this.pattern = {
                email: /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i,
                phone : /^\d{2,3}-\d{3,4}-\d{4}$/,
                notSpecialSymbol: /[^a-z|0-9|ㄱ-ㅎ|ㅏ-ㅣ|가-힣]/i
            };
            this.setTypes();
        }
        setTypes(){
            this.types.isNotEmpty = {
                validate: function(value){
                    return value !== "";
                },
                instructions: " 값은 필수 입니다."
            };
            this.types.isNumber = {
                validate: function(value){
                    return !isNaN(value);
                },
                instructions: " 숫자만 사용할 수 있습니다."
            };
            this.types.isEmail = {
                validate: (value) => {
                    return this.pattern.email.test(value);
                },
                instructions: "이메일 현태가 올바르지 않습니다. id@site.com"
            };
            this.types.isPhone = {
                validate: (value) => {
                    return this.pattern.phone.test(value);
                },
                instructions: "핸드폰번호 형태가 올바르지 않습니다. 010-0000-0000"
            };
            this.types.isCharNum = {
                validate: (value) => {
                    return !this.pattern.notSpecialSymbol.test(value);
                }
            }
        }
    }
    window.AVT = new Validator();
})();

 

이제 config 옵션을 set 해주는 setConfig 메소드와 에러여부를 체크하는 hasError, 에러를 확인하는 getError,

설정된 config에 따라 유효성 검증을 하는 validate 메소드를 구현해줍니다.

 

(function(){
    class Validator{
        constructor(){
            this.msg = [];
            this.types = {};
            this.conig = {};
            this.pattern = {
                email: /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i,
                phone : /^\d{2,3}-\d{3,4}-\d{4}$/,
                notSpecialSymbol: /[^a-z|0-9|ㄱ-ㅎ|ㅏ-ㅣ|가-힣]/i
            };
            this.setTypes();
        }
        setTypes(){
            this.types.isNotEmpty = {
                validate: function(value){
                    return value !== "";
                },
                instructions: " 값은 필수 입니다."
            };
            this.types.isNumber = {
                validate: function(value){
                    return !isNaN(value);
                },
                instructions: " 숫자만 사용할 수 있습니다."
            };
            this.types.isEmail = {
                validate: (value) => {
                    return this.pattern.email.test(value);
                },
                instructions: "이메일 현태가 올바르지 않습니다. id@site.com"
            };
            this.types.isPhone = {
                validate: (value) => {
                    return this.pattern.phone.test(value);
                },
                instructions: "핸드폰번호 형태가 올바르지 않습니다. 010-0000-0000"
            };
            this.types.isCharNum = {
                validate: (value) => {
                    return !this.pattern.notSpecialSymbol.test(value);
                }
            }
        }
        validate(data){
            this.msg = [];
            const configKeys = Object.keys(this.config);
            if(configKeys.length <= 0) {
                throw {
                    name: "ValidationError",
                    msg : `${this.prefix} config 옵션이 존재하지 않습니다!`
                }
            }
            for(let [key, types] of Object.entries(this.config)){
                if(data.hasOwnProperty(key)){
                    for(let type of types){
                        const checker = this.types[type];
                        if(typeof checker === "undefined"){
                            throw {
                                name: "ValidationError",
                                msg : `${type} 유효성 검사 로직이 존재하지 않습니다!`
                            }
                        }else{
                            const result = checker.validate(data[key]);
                            if(!result){
                                this.msg.push(`${key} 은(는) ${checker.instructions}`);
                            }
                        }
                    }
                }
            }
        }
        setConfig(config){
            this.config = config;
        }
        hasError(){
            return this.msg.length !== 0;
        }
        getError(){
            this.msg.unshift(this.prefix);
            return `${this.msg.join("\n")}`;
        }
    }
    window.AVT = new Validator();
})();

 

validate 메소드 내에서는 config 옵션에 해당하는 type을  checker로 선언하고 checker의 validate 메소드를 실행하여 결과를 얻습니다. 결과가 false일 경우 msg 배열에 checker의 instructions 를 넣어줍니다.

 

이제 사용을 해보도록 하죠.

 

const data = {
    name: "geonle!e",
    age: "37d",
    email: "geonleekakao.com",
    phone: "010-50422100"
}
const config = {
    name: ["isNonEmpty", "isCharNum"],
    age: ["isNumber"],
    email: ["isEmail"],
    phone: ["isPhone"],
}
AVT.setConfig(config);
AVT.validate(data);
if(AVT.hasError()){
    console.error(AVT.getError());
}

 

config 에 검증할 validator type을 배열로 넘기고 결과를 보도록 하죠.

name은 빈값이 아니고, 특수문자가 없어야도고, age는 숫자만, email과 phone은 각각의 형태를 맞춰주어야 오류가 발생하지 않습니다.

위의 예시에는 name에 !가, age에 d가 있어 유효성 검증에 걸리게 되고, email과 phone도 포멧이 올바르지 않습니다.

 

 

실행결과는 위와 같습니다.

 

이처럼 런타임 시에 config에 설정된 로직이 실행되게 하는 패턴이 전략패턴입니다.

로직이 추가되어야 할 경우에는 setTypes에 로직을 추가해주시면 됩니다.

반응형