개요

데이터 구조의 특정 값을 확인 똑같은 동작을 수행하는 코드가 중복이 되는 경우는 흔하다.

같은 중복을 곳으로 모으는 것이 좋다

 

특이 케이스 패턴

특수한 경우의 공통 동작을 요소 하나에 모아서 사용하는

 

특이 케이스 패턴을 활용하면 특이 케이스를 확인하는 코드 대부분을 단순한 함수 호출로 바꿀 있다.

 

특이 케이스 객체에서 단순히 데이터만 읽는 다면 리터럴 객체로 만든다.

어떤 동작을 수행해야한다면 일반 객체로 만든다.

 

특이 케이스 객체 가장 유명한게 객체 패턴이다.

예시



//전력 회사 가정, 현장에 인프라를 설치해 서비스를 제공할 것
class Site{
    get customer(){return this._customer;}
}
class Customer{
    get name(){return this._name;}//고객이름
    get buillingPlan(){return this._buillingPlan;}//요금제
    set buillingPlan(arg){this._buillingPlan= arg;}
    get paymentHistory(){return this._paymentHistory;}납부이력
}
//클라리언트1이라고 가정
function client1(){
    const aCustomer = site.customer;
    /**코드 들..*/
    let customerName;
    if(aCustomer === "미확인 고객") customerName = "거주자";
    else customerName =aCustomer.name;
}
//클라이언트2
function client2(){
    const plan = (aCustomer === "미확인 고객")
        ? registry.buillingPlans.basic
        : aCustomer.buillingPlan;
    /**코드 들..*/
}
//클라이언트3
function client3(){
    if(aCustomer !== "미확인 고객") aCustomer.buillingPlan = newPlan;
    /**코드 들..*/
}
//클라이언트4
function client4(){
    const weeksDelinquent = (aCustomer === "미확인 고객")
        ? 0
        : aCustomer.paymentHistory.weeksDelinquentInLastYear;
}
현장에는 고객이 거주하기도 하지만 그렇지 않다
최근 누군가 이사왔거나, 이사 갔을 수도 있다.(특이 케이스)
이런 식별이 안되는 고객을 "미확인 고객" 문자열로 채운다.


class Customer{
    get name(){return this._name;}//고객이름
    get buillingPlan(){return this._buillingPlan;}//요금제
    set buillingPlan(arg){this._buillingPlan= arg;}
    get paymentHistory(){return this._paymentHistory;}//납부이력
    //미확인 고객구분 메서드
    get isUnknown(){return false;}
}
//미확인 고객 전용 클래스, 서브클래스 아님!!!!!
class UnknownCustomer{
    get isUnknown(){return true;}
}
자바스크립트는 동적 언어로 굳이 서브클래스로 만들지 않았다.
//한번에 클래스 사용하도록 하기 부담됨, 임시로 수정할 코드들을 함수로 모아둠
function isUnknown(arg){
    if(!((arg instanceof Customer) || (arg === "미확인 고객")))
        throw new Error(`잘못된 값과 비교 : <${arg}>`);
    return (arg === "미확인 고객");
}
"미확인 고객" 고객을 리턴하는 모든 곳에 전용 클래스를 리턴하게 하고 isUnknown()메서드를 사용하도록 고쳐야 한다.
이를 적용하려면 번에 일괄적으로 변경해야하는 위험부담이 크다.
그래서 일단 수정해야하는 코드를 별도 함수로 추출해 모아놨다.


//클라리언트1이라고 가정
function client1(){
    const aCustomer = site.customer;
    /**코드 들..*/
    let customerName;
    if(isUnknown(aCustomer)) customerName = "거주자";
    else customerName =aCustomer.name;
}
//클라이언트2
function client2(){
    const plan = (isUnknown(aCustomer))
        ? registry.buillingPlans.basic
        : aCustomer.buillingPlan;
    /**코드 들..*/
}
//클라이언트3
function client3(){
    if(!isUnknown(aCustomer)) aCustomer.buillingPlan = newPlan;
    /**코드 들..*/
}
//클라이언트4
function client4(){
    const weeksDelinquent = (isUnknown(aCustomer))
        ? 0
        : aCustomer.paymentHistory.weeksDelinquentInLastYear;
}
//Site에서 특이 케이스일 때 전용 객채 반환하도록 수정
class Site{
    get customer(){return this._customer === "미확인 고객"
        ? new UnknownCustomer()
        : this._customer;}
}
//"미확인 고객" 문자열 확인하는 코드가 사라짐
function isUnknown(arg){
    if (!(arg instanceof Customer || arg instanceof UnknownCustomer))
        throw new Error(`잘못된 값과 비교 <${arg}>`);
    return arg.isUnknown;
}


//미확인 고객 전용 클래스
class UnknownCustomer{
    get isUnknown(){return true;}
    //특이 케이스 검사를 일반적인 기본값으로 대체할 수 있다.
    get name(){return "거주자";}
}




function client1(){
    /*
    const aCustomer = site.customer;
    let customerName;
    if(isUnknown(aCustomer)) customerName = "거주자";
    */
    const aCustomer = aCustomer.name;
}



class UnknownCustomer{
    get isUnknown(){return true;}
    get name(){return "거주자";}
    get buillingPlan(){return registry.buillingPlans.basic;}
    set buillingPlan(arg){/*무시*/}
}




function client2(){//읽는 경우
    const plan = aCustomer.buillingPlan;
}
function client3(){//쓰는 경우
    aCustomer.buillingPlan = newPlan;
}



class Customer{
    get name(){return this._name;}
    get buillingPlan(){return this._buillingPlan;}
    set buillingPlan(arg){this._buillingPlan= arg;}
    get paymentHistory(){return this._paymentHistory;}
    get isUnknown(){return false;}
}


class UnknownCustomer{
    get isUnknown(){return true;}
    get name(){return "거주자";}
    get buillingPlan(){return registry.buillingPlans.basic;}
    set buillingPlan(arg){/*무시*/}
    get paymentHistory(){return new NullPaymentHistory();}
}
class NullPaymentHistory{
    get weeksDelinquentInLastYear(){return 0;}
}




function client4(){
    const weeksDelinquent = aCustomer.paymentHistory.weeksDelinquentInLastYear;
}

완료


class Site{
    get customer(){return this._customer === "미확인 고객"
        ? new UnknownCustomer()
        : this._customer;}
}
class Customer{
    get name(){return this._name;}
    get buillingPlan(){return this._buillingPlan;}
    set buillingPlan(arg){this._buillingPlan= arg;}
    get paymentHistory(){return this._paymentHistory;}
    get isUnknown(){return false;}
}
class UnknownCustomer{
    get isUnknown(){return true;}
    get name(){return "거주자";}
    get buillingPlan(){return registry.buillingPlans.basic;}
    set buillingPlan(arg){/*무시*/}
    get paymentHistory(){return new NullPaymentHistory();}
}
class NullPaymentHistory{
    get weeksDelinquentInLastYear(){return 0;}
}
function isUnknown(arg){
    if (!(arg instanceof Customer || arg instanceof UnknownCustomer))
        throw new Error(`잘못된 값과 비교 <${arg}>`);
    return arg.isUnknown;
}
function client1(){
    const aCustomer = aCustomer.name;
}
function client2(){
    const plan = aCustomer.buillingPlan;
}
function client3(){
    aCustomer.buillingPlan = newPlan;
}
function client4(){
    const weeksDelinquent = aCustomer.paymentHistory.weeksDelinquentInLastYear;
}

 

"미확인 고객" 아닌 다른 값을 원하는 튀는 클라이언트가 있을 있다.

예를 들어 똑같은 상황이지만, "미확인 거주자" 리턴받고 싶은 클라이언트도 있을 있다. 이때는 그대로 유지한다.

 

 

예시: 객체 리터럴 이용하기

단순한 값을 위해서 클래스까지 만드는 것은 과한 감이 있다.

정보가 갱신될 있다면 클래스가 필요하다. 반대로 데이터 구조를 읽기만 한다면 클래스 대신 리터럴 객체를 사용해도 된다.

 

//고객 정보를 갱신하는 클라이언트가 없음
class Site{
    get customer(){return this._customer;}
}
class Customer{
    get name(){}
    get billingPlan(){}
    set billingPlan(arg){}
    get paymentHistory(){}
}
function client1(){
    const aCustomer = site.customer;
    /*코드 들*/
    let customerName;
    if(aCustomer === "미확인 고객") customerName = "거주자";
    else customerName = aCustomer.name;
}
function client2(){
    const plan = (aCustomer === "미확인 고객")
        ? registry.billingPlans.basic
        : aCustomer.billingPlan;
}
function client3(){
    const weeksDelinquent = (aCustomer === "미확인 고객")
        ? 0
        : aCustomer.paymentHistory.weeksDelinquentInLastYear;
}
// isUnknown 게터 정의 및 리터럴 객체 생성
class Customer{
    get name(){}
    get billingPlan(){}
    set billingPlan(arg){}
    get paymentHistory(){}
    get isUnknown(){return false;}
}
function createUnknownCustomer(){
    return {
        isUnknown : true,
    };
}


//특이 케이스 조건 검사 부분 함수로 추출, 및 사용
function isUnknown(arg){
    return (arg === "미확인 고객");
}
function client1(){
    const aCustomer = site.customer;
    /*코드 들*/
    let customerName;
    if(isUnknown(aCustomer)) customerName = "거주자";
    else customerName = aCustomer.name;
}
function client2(){
    const plan = isUnknown(aCustomer)
        ? registry.billingPlans.basic
        : aCustomer.billingPlan;
}
function client3(){
    const weeksDelinquent = isUnknown(aCustomer)
        ? 0
        : aCustomer.paymentHistory.weeksDelinquentInLastYear;
}


//이제 조건 검사하는 코드를 클래스로 옮긴다.
class Customer{
    get name(){}
    get billingPlan(){}
    set billingPlan(arg){}
    get paymentHistory(){}
    get isUnknown(){return (this._customer === "미확인 고객")?createUnknownCustomer():this._customer;}
}
function createUnknownCustomer(){
    return {
        isUnknown : true,
    };
}
function isUnknown(arg){
    return arg.isUnknown;
}


//표준 응답을 리터럴 값으로 대체
function createUnknownCustomer(){
    return {
        isUnknown : true,
        name: "거주자",
    };
}
function isUnknown(arg){
    return arg.isUnknown;
}
function client1(){
    let customerName = aCustomer.name;
}


//표준 응답을 리터럴 값으로 대체
function createUnknownCustomer(){
    return {
        isUnknown : true,
        name: "거주자",
        billingPlan : registry.billingPlans.basic,
    };
}
function isUnknown(arg){
    return arg.isUnknown;
}
function client1(){
    let customerName = aCustomer.name;
}
function client2(){
    const plan = aCustomer.billingPlan;
}


class Site{
    get customer(){return this._customer;}
}
class Customer{
    get name(){}
    get billingPlan(){}
    set billingPlan(arg){}
    get paymentHistory(){}
    get isUnknown(){return (this._customer === "미확인 고객")?createUnknownCustomer():this._customer;}
}
//표준 응답을 리터럴 값으로 대체
function createUnknownCustomer(){
    return {
        isUnknown : true,
        name: "거주자",
        billingPlan : registry.billingPlans.basic,
        paymentHistory : {
            weeksDelinquentInLastYear : 0,
        },
    };
}
function isUnknown(arg){
    return arg.isUnknown;
}
function client1(){
    let customerName = aCustomer.name;
}
function client2(){
    const plan = aCustomer.billingPlan;
}
function client3(){
    const weeksDelinquent =aCustomer.paymentHistory.weeksDelinquentInLastYear;
}

 

예시: 변환 함수 이용하기

변환 단계를 추가하면 레코드에도 적용할 있다.



const record = {
    name : "애크미 보스턴",
    location : "Malden MA",
    //여러 정보들...
    customer : {
        name: "애크미 산업",
        billingPlan:"plan-451",
        paymentHistory:{
            weeksDeliquentInLastYear: 7,
            //여러 정보들...
        },
        /*....... */
    }
}
const record2 ={
    name : "물류창고 15",
    location : "Malden MA",
    //여러 정보들...
    customer:"미확인 고객",
}
//클라이언트1
function client1(){
    const site = acquireSiteData();
    const aCustomer = site.customer;
    //여러 코드...
    let customerName;
    if(aCustomer === "미확인 고객") customerName = "거주자";
    else customerName = aCustomer.name;
}
function client2(){
    const plan = (aCustomer === "미확인 고객")
        ? registry.billingPlans.basic
        : aCustomer.billingPlan;
}
function client3(){
    const weeksDelinquent = (aCustomer === "미확인 고객")
        ? 0
        : aCustomer.paymentHistory.weeksDeliquentInLastYear;
}


//클라이언트1
function client1(){
    const rawSite = acquireSiteData();
    const site = enrichSite(rawSite);
    const aCustomer = site.customer;
    //여러 코드...
    let customerName;
    if(aCustomer === "미확인 고객") customerName = "거주자";
    else customerName = aCustomer.name;
    function enrichSite(inputSite){
        return _.cloneDeep(inputSite);
    }
}


//클라이언트1
function client1(){
    const rawSite = acquireSiteData();
    const site = enrichSite(rawSite);
    const aCustomer = site.customer;
    //여러 코드...
    let customerName;
    if(isUnknown(aCustomer)) customerName = "거주자";
    else customerName = aCustomer.name;
    function enrichSite(inputSite){
        return _.cloneDeep(inputSite);
    }
}
function isUnknown(aCustomer){
    return aCustomer === "미확인 고객";
}
function client2(){
    const plan = isUnknown(aCustomer)
        ? registry.billingPlans.basic
        : aCustomer.billingPlan;
}
function client3(){
    const weeksDelinquent = isUnknown(aCustomer)
        ? 0
        : aCustomer.paymentHistory.weeksDeliquentInLastYear;
}


    //변환함수 보강
    function enrichSite(inputSite){
        const result = _.cloneDeep(inputSite);
        const unknownCustomer = {
            isUnknown: true,
        };
        if(isUnknown(result.customer)) result.customer = unknownCustomer;
        else result.customer.isUnknown =false;
        return result;
    }


//변환함수 고려한 수정
function isUnknown(aCustomer){
    if (aCustomer === "미확인 고객") return true;
    else return aCustomer.isUnknown;
}


//클라이언트1
function client1(){
    const rawSite = acquireSiteData();
    const site = enrichSite(rawSite);
    const aCustomer = site.customer;
    //여러 코드...
    const customerName = aCustomer.name;
}
function enrichSite(inputSite){
    const result = _.cloneDeep(inputSite);
    const unknownCustomer = {
        isUnknown: true,
        name: "거주자", //이름 옮기기
    };
    if(isUnknown(result.customer)) result.customer = unknownCustomer;
    else result.customer.isUnknown =false;
    return result;
}


function enrichSite(inputSite){
    const result = _.cloneDeep(inputSite);
    const unknownCustomer = {
        isUnknown: true,
        name: "거주자",
        billingPlan :registry.billingPlans.basic, // 요금제 옮기기
    };
    if(isUnknown(result.customer)) result.customer = unknownCustomer;
    else result.customer.isUnknown =false;
    return result;
}


function client2(){
    const plan = aCustomer.billingPlan;
}


const record = {
    name : "애크미 보스턴",
    location : "Malden MA",
    //여러 정보들...
    customer : {
        name: "애크미 산업",
        billingPlan:"plan-451",
        paymentHistory:{
            weeksDeliquentInLastYear: 7,
            //여러 정보들...
        },
        /*....... */
    }
}
const record2 ={
    name : "물류창고 15",
    location : "Malden MA",
    //여러 정보들...
    customer:"미확인 고객",
}
//클라이언트1
function client1(){
    const rawSite = acquireSiteData();
    const site = enrichSite(rawSite);
    const aCustomer = site.customer;
    //여러 코드...
    const customerName = aCustomer.name;
}
function enrichSite(inputSite){
    const result = _.cloneDeep(inputSite);
    const unknownCustomer = {
        isUnknown: true,
        name: "거주자",
        billingPlan :registry.billingPlans.basic,
        paymentHistory : {
            weeksDeliquentInLastYear : 0,
        },
    };
    if(isUnknown(result.customer)) result.customer = unknownCustomer;
    else result.customer.isUnknown =false;
    return result;
}
function isUnknown(aCustomer){
    if (aCustomer === "미확인 고객") return true;
    else return aCustomer.isUnknown;
}
function client2(){
    const plan = aCustomer.billingPlan;
}
function client3(){
    const weeksDelinquent = aCustomer.paymentHistory.weeksDeliquentInLastYear;
}

 

+ Recent posts