배경
비슷한 대상들을 특정 특성에 따라 구분해야 할 때, 구분 기준으로 타입 코드를 사용하는 경우가 있다.
언어에서 열거형을 지원하면 보통 열거형으로 한다.
타입 코드 대신 서브클래스로 바꾸면 두 가지 장점이 있다.
첫째, 조건에 따라 다르게 동작하도록 해주는 다형성을 제공한다.
둘째, 특정 타입에서만 의미가 있는 값을 사용하는 필드나 메서드가 있을 때다.
이 리팩터링은 대상 클래스에 직접 적용할지, 아니면 타입 코드 자체에 적용할지 고민해야 한다.
전자는 서브클래스를 만들면 된다.
후자는 직원에게 직원 유형 속성을 부여하고 이 속성을 클래스로 정의하고 서브클래싱한다.
예시: 직접 상속할 때
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})`; } } |
상태 패턴과 비슷하다.
'IT책, 강의 > 리팩터링' 카테고리의 다른 글
12 - 상속 다루기 - 슈퍼클래스 추출하기 (1) | 2023.11.01 |
---|---|
12 - 상속 다루기 - 서브클래스 제거하기 (0) | 2023.10.31 |
12 - 상속 다루기 - 필드 내리기 (0) | 2023.10.29 |
12 - 상속 다루기 - 메서드 내리기 (0) | 2023.10.28 |
12 - 상속 다루기 - 생성자 본문 올리기 (0) | 2023.10.27 |