배경

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

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

 

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

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

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

 

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

 

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

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

 

예시: 직접 상속할



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})`;
    }
}

상태 패턴과 비슷하다.

개요

서브클래스에서만 사용하는 필드를 내린다.

예시


class Employee{
    protected String name;
}
class Salesperson extends Employee{
}
class Engineer extends Employee{
}


class Employee{
}
class Salesperson extends Employee{
}
class Engineer extends Employee{
    private String name;
}

 

개요

특정 서브클래스에만 관련된 메서드는 슈퍼 클래스에서 제거하고 해당 서브클래스로 옮긴다.

리팩터링은 해당 기능을 제공하는 서브클래스에 대해 정확히 파악한 경우에 적용할 있다. 그렇지 않다면 조건부 로직을 다형성으로 바꿔야 한다.

 


class Employee{
    get quota{}
}
class Engineer extends Employee{}
class Salesperson extends Employee{}
class Employee{
}
class Engineer extends Employee{}
class Salesperson extends Employee{
    get quota{}
}

개요

생성자는 특수한 메서드이기 때문에 리팩터링도 다른 식으로 접근해야 한다.

리팩터링이 간단히 끝날 같지 않다면 생성자를 팩터리 함수로 바꾸기를 고려한다.

 

예시



class Party{}
class Employee extends Party{
    constructor(name, id, monthlyCost){
        super();
        this._id = id;
        this._name = name;
        this._monthlyCost = monthlyCost;
    }
}
class Department extends Party{
    constructor(name, staff){
        super();
        this._name;
        this._staff = staff;
    }
}


class Party{
    constructor(name){
        this._name = name;
    }
}
class Employee extends Party{
    constructor(name, id, monthlyCost){
        super(name);
        this._id = id;
        this._monthlyCost = monthlyCost;
    }
}
class Department extends Party{
    constructor(name, staff){
        super(name);
        this._staff = staff;
    }
}

 

 

예시 : 공통 코드가 나중에



class Employee{
    constructor(name){
    }
    get isPrivileged(){}
    assignCar(){}
}
//예시 : 공통 코드가 나중에 올때
class Manager extends Employee{
    constructor(name, grade){
        super(name);
        this._grade = grade;
        if(this.isPrivileged) this.assignCar();//모든 서브클래스가 수행함
    }
    get isPrivileged(){
        return this._grade >4;
    }
}


class Manager extends Employee{
    constructor(name, grade){
        super(name);
        this._grade = grade;
        this.finishConstruction();
    }
    //먼저 공통 코드를 함수로 추출
    finishConstruction() {
        if (this.isPrivileged)
            this.assignCar();
    }
    get isPrivileged(){
        return this._grade >4;
    }
}


class Employee{
    constructor(name){
    }
    get isPrivileged(){}
    assignCar(){}
    //슈퍼클래스로 옮김
    finishConstruction() {
        if (this.isPrivileged)
            this.assignCar();
    }
}
class Manager extends Employee{
    constructor(name, grade){
        super(name);
        this._grade = grade;
        this.finishConstruction();
    }
    get isPrivileged(){
        return this._grade >4;
    }
}

 

개요

서브 클래스들이 독립적으로 개발되다가 하나의 계층구조로 리팩터링된 경우라면 일부 기능이 중복될 있다.

 

예시



public class Refactoring1 {
    static class Employee{
       
    }
    static class Salesperson extends Employee{
        private String name;
    }
    static class Engineer extends Employee{
        private String name;
    }
}


public class Refactoring1 {
    static class Employee{
        protected String name;
    }
    static class Salesperson extends Employee{
    }
    static class Engineer extends Employee{
    }
}

개요

중복 코드 제거는 중요하다. 당장 문제 없더라도 미래에 요구사항 변경에 따른 코드 수정 모든 코드에 반영되지 않을 있다는 위험이 존재한다.

예시

실질적으로 같은 메서드
class Party{
}
class Employee extends Party{
    get annualCost(){
        return this.monthlyCost *12 ;
    }
}
class Department extends Party{
    get totalAnnualCost(){
        return this.monthlyCost *12 ;
    }
}


class Party{
    get annualCost(){
        return this.monthlyCost *12 ;
    }
}
//서브 클래스 중 하나로 이름을 통일하고, 슈퍼 클래스에 메서드를 복사한다.
class Employee extends Party{
    get annualCost(){
        return this.monthlyCost *12 ;
    }
}
class Department extends Party{
    get annualCost(){
        return this.monthlyCost *12 ;
    }
}


class Party{
    get annualCost(){
        return this.monthlyCost *12 ;
    }
}
//정상동작을 확인했다면 서브클래스 메서드를 제거한다.
class Employee extends Party{
}
class Department extends Party{
}


class Party{
    get annualCost(){
        return this.monthlyCost *12 ;
    }
    //Party 슈퍼클래스에서는 서브 클래스에 monthlyCost가 없다.
    //이대로 동작하는 이유는 자바스크립트는 동적언어이기 때문이다.
    //이를 알리기 위해 함정 메서드를 만들어두는 것이 도움된다.
    get monthlyCost(){
        throw new Error("이 값은 서브클래스에서 구현해야합니다.");
    }
}
//정상동작을 확인했다면 서브클래스 메서드를 제거한다.
class Employee extends Party{
    //monthlyCost 있다고 가정
}
class Department extends Party{
    //monthlyCost 있다고 가정
}

 

 

개요

예외라는 개념은 오류 코드를 연쇄적으로 전파하던 코드를 제거할 있게 해줬다.

예외는 과용하면 좋지 않다. 말그대로 예외인 경우에만 쓰여야 한다.

함수 호출 전에 문제를 제거할 있다면 그렇게 해야 한다.

예시

DB 연결 같은 자원을 관리하는 자원 클래스

풀은 어떤 자원이 할당되고 가용할 있는 지를 추적, 자원 바닥나면 새로 생성

 



import java.util.Deque;
import java.util.List;
import java.util.NoSuchElementException;
public class Refactoring1 {
    static class Resource{
        public static Resource create() {
            return null;
        }
    }
    static class ResourcePool{
        private Deque<Resource> available;
        private List<Resource> allocated;
       
        public Resource get() {
            Resource result;
            try {
                result = available.pop();
                allocated.add(result);
            }catch (NoSuchElementException e) {
                result = Resource.create();
                allocated.add(result);
            }
            return result;
        }
    }
}


import java.util.Deque;
import java.util.List;
import java.util.NoSuchElementException;
public class Refactoring1 {
    static class Resource{
        public static Resource create() {
            return null;
        }
    }
    static class ResourcePool{
        private Deque<Resource> available;
        private List<Resource> allocated;
       
        public Resource get() {
            Resource result;
            //풀에서 자원이 고갈되는 건 예상치 못한 조건이 아니다. 따라서 예외처리로 대응하는 건 옳지 않다.
            if(available.isEmpty()) {
                result = Resource.create();
                allocated.add(result);
            }else {
                try {
                    result = available.pop();
                    allocated.add(result);
                }catch (NoSuchElementException e) {
                    throw new AssertionError("도달 불가");
                }
            }
            return result;
        }
    }
}
예외에 도달하지 못한다면 이제 제거
import java.util.Deque;
import java.util.List;
public class Refactoring1 {
    static class Resource{
        public static Resource create() {
            return null;
        }
    }
    static class ResourcePool{
        private Deque<Resource> available;
        private List<Resource> allocated;
       
        public Resource get() {
            Resource result;
            if(available.isEmpty()) {
                result = Resource.create();
                allocated.add(result);
            }else {
                result = available.pop();
                allocated.add(result);
            }
            return result;
        }
    }
}


import java.util.Deque;
import java.util.List;
public class Refactoring1 {
    static class Resource{
        public static Resource create() {
            return null;
        }
    }
    static class ResourcePool{
        private Deque<Resource> available;
        private List<Resource> allocated;
       
        public Resource get() {
            //더 다듬기
            Resource result = available.isEmpty()? Resource.create():available.pop();
            allocated.add(result);
            return result;
        }
    }
}

 

 

개요

과거 오류 처리는 오류 코드를 통해서 했다.

 

예외는 프로그래밍 언어에서 제공하는 독립적인 오류 처리 메커니즘이다

오류가 발견되면 예외를 던지고, 생성된 예외는 적절한 예외 핸들러를 찾을 때까지 콜스택을 타고 위로 전파된다.

예외를 사용하면 오류코드를 일일이 검사하거나 오류를 식별하는 일을 신경 쓰지 않아도 된다.

 

예외는 정확히 예상 밖의 동작일 때만 쓰여야 한다.

판단하기 도움되는 기준은 예외를 던지는 코드를 프로그램 종료 코드로 바꿔도 프로그램이 여전히 정상동작하는지 보는것이다. 정상 동작하지 않는다면 예외를 사용하지 말고, 오류를 검출하여 프로그램을 정상 흐름으로 되돌려야 한다.

 

예시

전역 테이블에서 배송지의 배송 규칙을 알아내는 코드



function localShippingRules(country){//country 는 항상 유효함을 가정
    const data = countryData.shippingRules[country];
    if(data) return new shippingRules(data);
    else return -23; //오류
}
function calculateShippingCosts(anOrder){
    //코드들...
    const shippingRules = localShippingRules(anOrder.country);
    if(shippingRules < 0) return shippingRules; //오류 전파
    //코드들...
}
const status = calculateShippingCosts(orderData);
if(status < 0) errorList.push({order:orderData, errorCode:statuts});


try {//최상위 예외 핸들러를 만든다.
    const status = calculateShippingCosts(orderData);
} catch (error) {/*예외처리*/}
if(status < 0) errorList.push({order:orderData, errorCode:statuts});


//최상위
let status; //스코프 문제로 선언과 초기화를 어쩔 수 없이 분리
try {
    status = calculateShippingCosts(orderData);
} catch (error) {/*예외처리*/}
if(status < 0) errorList.push({order:orderData, errorCode:statuts});


//최상위
let status;
try {
    status = calculateShippingCosts(orderData);
} catch (error) {throw error;}//잡은 예외 다시 던지기
if(status < 0) errorList.push({order:orderData, errorCode:statuts});


//최상위
let status;
try {
    status = calculateShippingCosts(orderData);
} catch (error) {
    if(error instanceof OrderProcessingError)
        errorList.push({order:orderData, errorCode:statuts});
    else throw error;
    throw error;
}
if(status < 0) errorList.push({order:orderData, errorCode:statuts});
//별도 추가된 예외만들 처리하는 경우 처리방식
class OrderProcessingError extends Error{
    constructor(errorCode){
        super(`주문 처리 오류 : ${errorCode}`);
        this.code = errorCode;
    }
    get name(){return "OrderProcessingError";}
}


function localShippingRules(country){//country 는 항상 유효함을 가정
    const data = countryData.shippingRules[country];
    if(data) return new shippingRules(data);
    //오류 코드 대신 예외를 던진다.
    else return new OrderProcessingError(-23);
}


function calculateShippingCosts(anOrder){
    //코드들...
    const shippingRules = localShippingRules(anOrder.country);
    //이 상태로 두고 아직 오류 코드를 다 못처리한 부분있는지 테스트
    if(shippingRules < 0) return new Error("오류 코드가 다 사라지지 않았습니다.");
    //코드들...
}


function localShippingRules(country){//country 는 항상 유효함을 가정
    const data = countryData.shippingRules[country];
    if(data) return new shippingRules(data);
    //오류 코드 대신 예외를 던진다.
    else return new OrderProcessingError(-23);
}
function calculateShippingCosts(anOrder){
    //코드들...
    const shippingRules = localShippingRules(anOrder.country);
    //코드들...
}
//최상위
try {
    calculateShippingCosts(orderData);
} catch (error) {
    if(error instanceof OrderProcessingError)
        errorList.push({order:orderData, errorCode:statuts});
    else throw error;
    throw error;
}
class OrderProcessingError extends Error{
    constructor(errorCode){
        super(`주문 처리 오류 : ${errorCode}`);
        this.code = errorCode;
    }
    get name(){return "OrderProcessingError";}
}

 

 

개요

데이터가 어떻게 수정되는지 추적하는 일이 코드 파악에서 어려분 부분 하나다

 

특히 같은 데이터를 읽고 수정하는 곳이 여러 곳이라면 스크롤을 마구잡이로 이동하면서 봐야한다.

이런 이유로 데이터가 수정된 사실을 명확히 알리는 것은 중요하다.

수정 사실을 알리는 가장 좋은 것은 반환값으로 수정 대상 변수에 다시 담는 것이다.

코드 상으로도 수정된다는 사실이 명확해진다.

 

예시



let totalAscent = 0;
let totalTime = 0;
let totalDistance = 0;
calculateAscent();
calculateTime();
calculateDistance();
const pace = totalTime / 60 / totalDistance;
function calculateAscent(){
    for(let i = 1; i<ProcessingInstruction.length;i++){
        const verticalChange = points[i].elevation - points[i-1].elevation;
        totalAscent += (verticalChange>0) ? verticalChange : 0;
    }
}
totalAscent 갱신된다는 사실이 명확하지 않다.


let totalAscent = 0;
let totalTime = 0;
let totalDistance = 0;
totalAscent = calculateAscent();
calculateTime();
calculateDistance();
const pace = totalTime / 60 / totalDistance;
//값을 반환하도록 수정
function calculateAscent(){
    for(let i = 1; i<ProcessingInstruction.length;i++){
        const verticalChange = points[i].elevation - points[i-1].elevation;
        totalAscent += (verticalChange>0) ? verticalChange : 0;
    }
    return totalAscent;
}


function calculateAscent(){
    let totalAscent  = 0; //반환할 변수 선언
    for(let i = 1; i<ProcessingInstruction.length;i++){
        const verticalChange = points[i].elevation - points[i-1].elevation;
        totalAscent += (verticalChange>0) ? verticalChange : 0;
    }
    return totalAscent;
}


function calculateAscent(){
    let result  = 0; //적절한 이름으로 변경
    for(let i = 1; i<ProcessingInstruction.length;i++){
        const verticalChange = points[i].elevation - points[i-1].elevation;
        result += (verticalChange>0) ? verticalChange : 0;
    }
    return result;
}


const totalAscent = calculateAscent();
let totalTime = 0;
let totalDistance = 0;
calculateTime();
calculateDistance();
const pace = totalTime / 60 / totalDistance;
function calculateAscent(){
    let result  = 0; //적절한 이름으로 변경
    for(let i = 1; i<ProcessingInstruction.length;i++){
        const verticalChange = points[i].elevation - points[i-1].elevation;
        result += (verticalChange>0) ? verticalChange : 0;
    }
    return result;
}

const totalAscent = calculateAscent();
const totalTime = calculateTime();
const totalDistance = calculateDistance();
const pace = totalTime / 60 / totalDistance;

 

 

개요

명령 객체는 복잡한 연산을 다룰 있는 강력한 메커니즘을 제공한다.

연산을 하나를 여러 작은 메서드로 쪼갠다. 로컬 변수를 인스턴스 변수로 만들어 메서드 끼리 정보 공유할 수도 있다. 쪼게진 보조 메서드들은 좋은 디버깅 포인트가 된다.

 

다만, 복잡하지 않은 함수에 굳이 명령 사용하면 득보다 실이 크다.

 

예시



class ChargeCalculator{
    constructor(customer, usage, provider){
        this._customer = customer;
        this._usage = usage;
        this._provider = provider;
    }
    get baseCharge(){
        return this._customer.baseRate * this._usage;
    }
    get charge(){
        return this.baseCharge + this._provider.connectionCharge;
    }
}
//호출자...
const monthCharge = new ChargeCalculator(customer, usage, provider).charge;


//클래스를 생성하고 호출하는 함수 만들기
function charge(customer, usage, provider){
    return new ChargeCalculator(customer, usage, provider).charge;
}


class ChargeCalculator{
    constructor(customer, usage, provider){
        this._customer = customer;
        this._usage = usage;
        this._provider = provider;
    }
    get baseCharge(){
        return this._customer.baseRate * this._usage;
    }
    //보조함수 제거하기,먼저 로컬 변수로 추출
    get charge(){
        const baseCharge = this.baseCharge;
        return baseCharge + this._provider.connectionCharge;
    }
}


class ChargeCalculator{
    constructor(customer, usage, provider){
        this._customer = customer;
        this._usage = usage;
        this._provider = provider;
    }
    //보조함수 제거하기, 보조 함수 인라인 하기
    get charge(){
        const baseCharge = this._customer.baseRate * this._usage;
        return baseCharge + this._provider.connectionCharge;
    }
}


function charge(customer, usage, provider){
    return new ChargeCalculator(customer, usage, provider)
        .charge(customer, usage, provider);
}
class ChargeCalculator{
    constructor(customer, usage, provider){
        this._customer = customer;
        this._usage = usage;
        this._provider = provider;
    }
    //함수로 변경하기 위에 함수 포맷에 맞게 함수 선언 바꾸기 적용
    charge(customer, usage, provider){
        const baseCharge = this._customer.baseRate * this._usage;
        return baseCharge + this._provider.connectionCharge;
    }
}


function charge(customer, usage, provider){
    return new ChargeCalculator(usage, provider)
        .charge(customer, usage, provider);
}
class ChargeCalculator{
    constructor(usage, provider){
        this._usage = usage;
        this._provider = provider;
    }
    //인스턴스 변수를 하나씩 점진적으로 제거하면서 매개변수를 사용하도록 수정
    charge(customer, usage, provider){
        const baseCharge = customer.baseRate * this._usage;
        return baseCharge + this._provider.connectionCharge;
    }
}


function charge(customer, usage, provider){
    return new ChargeCalculator()
        .charge(customer, usage, provider);
}
class ChargeCalculator{
    //나머지도 하나씩 적용하면서 테스트
    charge(customer, usage, provider){
        const baseCharge = customer.baseRate * usage;
        return baseCharge + provider.connectionCharge;
    }
}
function charge(customer, usage, provider){
    const baseCharge = customer.baseRate * usage;
    return baseCharge + provider.connectionCharge;
}
//호출자...
const monthCharge = new ChargeCalculator(customer, usage, provider).charge;

 

 

 

+ Recent posts