배경

비슷한 대상들을 특정 특성에 따라 구분해야 , 구분 기준으로 타입 코드를 사용하는 경우가 있다.

언어에서 열거형을 지원하면 보통 열거형으로 한다.

 

타입 코드 대신 서브클래스로 바꾸면 가지 장점이 있다.

첫째, 조건에 따라 다르게 동작하도록 해주는 다형성을 제공한다.

둘째, 특정 타입에서만 의미가 있는 값을 사용하는 필드나 메서드가 있을 때다.

 

리팩터링은 대상 클래스에 직접 적용할지, 아니면 타입 코드 자체에 적용할지 고민해야 한다.

 

전자는 서브클래스를 만들면 된다.

후자는 직원에게 직원 유형 속성을 부여하고 속성을 클래스로 정의하고 서브클래싱한다.

 

예시: 직접 상속할



class Employee{
    constructor(name, type){
        this.validateType(type);
        this._name = name;
        this._type = type;
    }
    validateType(arg){
        if(!["engineer","manager","salesperson"].includes(arg))
            throw new Error(`${arg}라는 직운 유형은 없습니다.`);
    }
    toString(){return `${this._name} (${this._type})`;}
}


class Employee{
    constructor(name, type){
        this.validateType(type);
        this._name = name;
        this._type = type;
    }
    //타입 코드 캡슐화
    get tpye(){return this._type;}
    validateType(arg){
        if(!["engineer","manager","salesperson"].includes(arg))
            throw new Error(`${arg}라는 직운 유형은 없습니다.`);
    }
    toString(){return `${this._name} (${this.type})`;}//_ 없음
}


//서브 클래스 오버라이딩
class Engineer extends Employee{
    get type(){return "engineer";}
}


//생성자를 팩터리 함수로 대체
function createEmployee(name, type){
    return new Employee(name, type);
}


//생성자를 팩터리 함수로 대체
function createEmployee(name, type){
    switch(tpye){
        case "engineer" : return new Engineer(name,type);
    }
    return new Employee(name, type);
}


function createEmployee(name, type){
    switch(tpye){
        case "engineer" : return new Engineer(name,type);
        case "salesperson" : return new Salesperson(name,type);
        case "manager" : return new Manager(name,type);
    }
    return new Employee(name, type);
}
class Employee{
    constructor(name, type){
        this.validateType(type);
        this._name = name;
        this._type = type;
    }
    get tpye(){return this._type;}
    validateType(arg){
        if(!["engineer","manager","salesperson"].includes(arg))
            throw new Error(`${arg}라는 직운 유형은 없습니다.`);
    }
    toString(){return `${this._name} (${this.type})`;}
}
class Engineer extends Employee{
    get type(){return "engineer";}
}
class Salesperson extends Employee{
    get type(){return "salesperson";}
}
class Manager extends Employee{
    get type(){return "manager";}
}


class Employee{
    //모든 서브클래스에서 재정의한 필드와 게터제거
    constructor(name, type){
        this.validateType(type);
        this._name = name;
    }
    validateType(arg){
        if(!["engineer","manager","salesperson"].includes(arg))
            throw new Error(`${arg}라는 직운 유형은 없습니다.`);
    }
    toString(){return `${this._name} (${this.type})`;}
}


function createEmployee(name, type){
    switch(tpye){
        case "engineer" : return new Engineer(name,type);
        case "salesperson" : return new Salesperson(name,type);
        case "manager" : return new Manager(name,type);
        default : throw new Error(`${arg}라는 직운 유형은 없습니다.`);
    }
}
class Employee{
    //팩터리 메서드에서 검증은 대체가능 하니 제거
    constructor(name, type){
        this._name = name;
    }
    toString(){return `${this._name} (${this.type})`;}
}


//필요없어진 타입 코드 인자 제거
function createEmployee(name){
    switch(tpye){
        case "engineer" : return new Engineer(name);
        case "salesperson" : return new Salesperson(name);
        case "manager" : return new Manager(name);
        default : throw new Error(`${arg}라는 직운 유형은 없습니다.`);
    }
}
class Employee{
    constructor(name){
        this._name = name;
    }
    //서브 클래스에는 아직 type 코드 게터가 있다.
    toString(){return `${this._name} (${this.type})`;}
}
class Engineer extends Employee{
    get type(){return "engineer";}
}
class Salesperson extends Employee{
    get type(){return "salesperson";}
}
class Manager extends Employee{
    get type(){return "manager";}
}

 

 

 

예시 : 간접 상속할

이미 서브 클래스로 아르바이트와 정직원이라는 클래스가 존재해 Employee 직접 상속하는 방식으로는 타입 코드 문제에 대처할 없다고 가정

또한 직원 유형을 변경하는 기능을 유지하고 싶다는 것도 직접 상속을 사용하지 않는 이유



class Employee{
    constructor(name,type){
        this.validateType(type);
        this._name = name;
        this._type = type;
    }
    validateType(arg){
        if(!["engineer","manager","salesperson"].includes(arg))
            throw new Error(`${arg}라는 직원 유형은 없습니다.`);
    }
    get type(){return this._type;}
    set type(arg){this._type = arg;}
    get capitalizedType(){
        return this._type.charAt(0).toUpperCase() + this._type.substr(1).toLowerCase();
    }
    toString(){
        return `${this._name} (${this.capitalizedType})`;
    }
}


//타입 코드를 클래스로 만들기(기본형을 객체로 만듦)
class EmployeeType{
    constructor(aString){
        this._value = aString;
    }
    toString(){return this._value;}
}
class Employee{
    constructor(name,type){
        this.validateType(type);
        this._name = name;
        this._type = type;
    }
    validateType(arg){
        if(!["engineer","manager","salesperson"].includes(arg))
            throw new Error(`${arg}라는 직원 유형은 없습니다.`);
    }
    get typeString() {return this._type.toString();}
    get type(){return this._type;}
    set type(arg){this._type = arg;}
    get capitalizedType(){
        return this.typeString.charAt(0).toUpperCase()
        + this.typeString.substr(1).toLowerCase();
    }
    toString(){
        return `${this._name} (${this.capitalizedType})`;
    }
}


class EmployeeType{
}
class Engineer extends EmployeeType{
    toString(){return "engineer";}
}
class Salesperson extends EmployeeType{
    toString(){return "salesperson";}
}
class Manager extends EmployeeType{
    toString(){return "manager";}
}
class Employee{
    constructor(name,type){
        this._name = name;
        this._type =  this.createEmployeeType(type);
    }
    static createEmployeeType(aString){
        switch(aString){
            case "engineer" : return new Engineer();
            case "salesperson" : return new Salesperson();
            case "manager" : return new Manager();
            default : throw new Error(`${aString}라는 직원 유형은 없습니다.`);
        }
    }
    get typeString() {return this._type.toString();}
    get type(){return this._type;}
    set type(arg){this._type = Employee.createEmployeeType(arg);}
    get capitalizedType(){
        return this.typeString.charAt(0).toUpperCase()
        + this.typeString.substr(1).toLowerCase();
    }
    toString(){
        return `${this._name} (${this.capitalizedType})`;
    }
}


class EmployeeType{
    get capitalizedName(){
        return this.toString().charAt(0).toUpperCase()
        + this.toString().substr(1).toLowerCase();
    }
}
class Engineer extends EmployeeType{
    toString(){return "engineer";}
}
class Salesperson extends EmployeeType{
    toString(){return "salesperson";}
}
class Manager extends EmployeeType{
    toString(){return "manager";}
}
class Employee{
    constructor(name,type){
        this._name = name;
        this._type = this.createEmployeeType(type);
    }
    static createEmployeeType(aString){
        switch(aString){
            case "engineer" : return new Engineer();
            case "salesperson" : return new Salesperson();
            case "manager" : return new Manager();
            default : throw new Error(`${aString}라는 직원 유형은 없습니다.`);
        }
    }
    get typeString() {return this._type.toString();}
    get type(){return this._type;}
    set type(arg){this._type = Employee.createEmployeeType(arg);}


    toString(){
        return `${this._name} (${this.type.capitalizedName})`;
    }
}

상태 패턴과 비슷하다.

+ Recent posts