다형성으로 If-else문 리팩터링 하기

안지환·2024년 1월 26일

공부 기록

목록 보기
1/5

OverView

if-eles 문은 타입을 체크를 하지 않는 이상 사용하는 것을 고려해야 합니다.

코드 스멜로 인식하는 이른 바인딩(early binding)이 있습니다. 즉 컴파일이 실행 된 후 결정되기 때문에 재실행 없이는 수정이 불가능합니다. if-else 문은 늦은 바인딩(late binding)에 해당이 됩니다. if문은 실행 시점에서 결정되기 때문에 오류를 감지가 가능합니다.

코드에서는 if-else 문을 사용해 의사결정을 뒤로 미루는 형태를 가지고 있습니다. 이를 객체지향적 설계로 제어 흐름 연산자로 리팩터링을 진행하겠습니다.

예제코드

enum TShirtSizes {
  SMALL = 33, MEDIUM = 37, LARGE = 42,
}

const handleSelect = (tShirtSizes: TShirtSizes) => {
  if (tShirtSizes === TShirtSizes.SMALL) {
    return 33;
  } else if (tShirtSizes === TShirtSizes.MEDIUM) {
    return 37;
  } else if (tShirtSizes === TShirtSizes.LARGE) {
    return 42;
  }
};

describe('shirtSize', () => {
  context('셔츠 사이즈가 LARGE가 주어지면', () => {
    it('42 Size를 반환해야 한다.', () => {
      expect(handleSelect(TShirtSizes.LARGE)).toBe(42);
    });
  });

  context('셔츠 사이즈가 MEDIUM가 주어지면', () => {
    it('37 Size를 반환해야 한다.', () => {
      expect(handleSelect(TShirtSizes.MEDIUM)).toBe(37);
    });
  });

  context('셔츠 사이즈가 SMALL가 주어지면', () => {
    it('33 Size를 반환해야 한다.', () => {
      expect(handleSelect(TShirtSizes.SMALL)).toBe(33);
    });
  });
});

Task1: enum 열거형을 인터페이스로 변경하기

  • enum(열거형)에 값을 interface(인터페이스)로 전환합니다.
    • 열거형 값을 인터페이스 메서드 형태로 생성합니다.
enum TShirtSizes {
  SMALL = 33, MEDIUM = 37, LARGE = 42,
}

interface TShirtSizes2 {
  isSmall(): boolean;
  isMedium(): boolean;
  isLarge(): boolean;
}

Task2: 열거형 값을 클래스 생성하기

  • 열거형 값을 클래스로 생성 한 다음 인터페이스로 상속을 합니다.
    • 열거형 속성값들의 클래스를 생성합니다.
    • 클래스 내 메서드는 해당 클래스가 열거형 값과 동일하면 true 반환을 합니다.
enum TShirtSizes {
  SMALL = 33, MEDIUM = 37, LARGE = 42,
}

class Small implements TShirtSizes2 {
  isSmall(): boolean {
    return true;
  }

  isMedium(): boolean {
    return false;
  }

  isLarge(): boolean {
    return false;
  }
}

class Medium implements TShirtSizes2 {
  isSmall(): boolean {
    return false;
  }

  isMedium(): boolean {
    return true;
  }

  isLarge(): boolean {
    return false;
  }
}
...

Task3: if 조건문 매개변수 변경하기

handleSelect 함수 내 if 조건문 내에 매개변수를 새로운 매서드로 대체합니다.

const handleSelect = (tShirtSizes: TShirtSizes) => {
  if (tShirtSizes === TShirtSizes.SMALL) {
    return 33;
  } if (tShirtSizes === TShirtSizes.MEDIUM) {
    return 37;
  } if (tShirtSizes === TShirtSizes.LARGE) {
    return 42;
  }
};

handleSelect 함수 매개변수를 TShirtSizes2 인터페이스로 변경합니다.

interface TShirtSizes2 {
  isSmall(): boolean;
  isMedium(): boolean;
  isLarge(): boolean;
}

const handleSelect = (tShirtSizes: TShirtSizes2) => {
  if (tShirtSizes.isSmall()) {
    return 33;
  } if (tShirtSizes.isMedium()) {
    return 37;
  } if (tShirtSizes.isLarge()) {
    return 42;
  }
};

tShirtSizes.isSmall() 값이 33이 반환 될 수 있는 이유는 조건문이 true 조건 때문입니다.

Task4: 함수를 클래스 내 메서드로 이관하기

  • handleSelect 함수를 매개변수로 변경이 완료가 되었습니다.
const handleSelect = (tShirtSizes: TShirtSizes2) => {
  if (tShirtSizes.isSmall()) {
    return 33;
  } if (tShirtSizes.isMedium()) {
    return 37;
  } if (tShirtSizes.isLarge()) {
    return 42;
  }
};
  • 함수를 각 클래스 메서드로 복사 붙여 넣기를 합니다.
    • 각 클래스에 동일하게 복사 붙여 넣기를 합니다.
class Large implements TShirtSizes2 {
  isSmall(): boolean {
    return false;
  }

  isMedium(): boolean {
    return false;
  }

  isLarge(): boolean {
    return true;
  }

  handleSelect(tShirtSizes: TShirtSizes2) {
    if (tShirtSizes.isSmall()) {
      return 33;
    } if (tShirtSizes.isMedium()) {
      return 37;
    } if (tShirtSizes.isLarge()) {
      return 42;
    }
  }
}

인터페이스에 handleSelect 메서드를 추가합니다.

interface TShirtSizes2 {
  isSmall(): boolean;
  isMedium(): boolean;
  isLarge(): boolean;
  handleSelect(): number;
}

! Tip
이때 동일한 속성이 할당 됩니다. 이때 handleSelect
함수 내에 매개 변수를 제거하고 변수를 this로 변환해줍니다. 각 클래스에 동일하게 변경해줍니다.

class Small implements TShirtSizes2 {
  isSmall(): boolean {
    return true;
  }

  isMedium(): boolean {
    return false;
  }

  isLarge(): boolean {
    return false;
  }

  handleSelect(): number {
    if (this.isSmall()) {
      return 33;
    } if (this.isMedium()) {
      return 37;
    } if (this.isLarge()) {
      return 42;
    }
  }
}

Task5: 함수 내에 조건문 제거하기

  • 함수 내에 If 조건문을 제거합니다.
const handleSelect = (tShirtSizes: TShirtSizes2) => {
  if (tShirtSizes.isSmall()) {
    return 33;
  } if (tShirtSizes.isMedium()) {
    return 37;
  } if (tShirtSizes.isLarge()) {
    return 42;
  }
};
  • 인터페이스의 구현 된 handleSelect 메서드를 호출합니다.
const handleSelect = (tShirtSizes: TShirtSizes2) => tShirtSizes.handleSelect();

Task6: 각 클래스에 반환 값만 남기기

인터페이스 메서드로 호출이 완료가 되면 각 클래스의 해당 값만 뺀 나머지를 제거합니다.

class Large implements TShirtSizes2 {
  isSmall(): boolean {
    return false;
  }

  isMedium(): boolean {
    return false;
  }

  isLarge(): boolean {
    return true;
  }

  handleSelect() {
		return 42;
	}
}

class Small implements TShirtSizes2 {
  isSmall(): boolean {
    return true;
  }

  isMedium(): boolean {
    return false;
  }

  isLarge(): boolean {
    return false;
  }

  handleSelect(): number {
    return 33;
  }
}

class Medium implements TShirtSizes2 {
  isSmall(): boolean {
    return false;
  }

  isMedium(): boolean {
    return true;
  }

  isLarge(): boolean {
    return false;
  }

  handleSelect() {
    return 37;
  }
}

Task7: 인터페이스 메서드와 상속 제거하기

  • 영향이 없는 메서드를 제거해줍니다.
interface TShirtSizes2 {
  isSmall(): boolean;
  isMedium(): boolean;
  isLarge(): boolean;
  handleSelect(): number;
}
  • 접두어 is 붙은 메서드는 더 이상 호출하는 데 영향이 없습니다.
interface TShirtSizes2 {
  handleSelect(): number;
}
  • 각 클래스에 구현 된 접두어 is 메서드를 제거해줍니다.
class Small implements TShirtSizes2 {
  handleSelect(): number {
    return 33;
  }
}

class Medium implements TShirtSizes2 {
  handleSelect() {
    return 37;
  }
}

class Large implements TShirtSizes2 {
  handleSelect() {
    return 42;
  }
}

클래스에 상속 된 인터페이스를 제거합니다.

class Small {
  handleSelect(): number {
    return 33;
  }
}

class Medium {
  handleSelect() {
    return 37;
  }
}

class Large {
  handleSelect() {
    return 42;
  }
}

Task8: 최종 구현 형태

interface TShirtSizes {
  handleSelect(): number;
}

class Small {
  handleSelect(){
    return 33;
  }
}

class Medium {
  handleSelect() {
    return 37;
  }
}

class Large {
  handleSelect() {
    return 42;
  }
}

const handleSelect = (tShirtSizes: TShirtSizes) => tShirtSizes.handleSelect();

describe('shirtSize', () => {
  context('셔츠 사이즈가 LARGE가 주어지면', () => {
    it('42 Size를 반환해야 한다.', () => {
      expect(handleSelect(new Large())).toBe(42);
    });
  });

  context('셔츠 사이즈가 MEDIUM가 주어지면', () => {
    it('37 Size를 반환해야 한다.', () => {
      expect(handleSelect(new Medium())).toBe(37);
    });
  });

  context('셔츠 사이즈가 SMALL가 주어지면', () => {
    it('33 Size를 반환해야 한다.', () => {
      expect(handleSelect(new Small())).toBe(33);
    });
  });
});

handleSelect 함수 내에 인터페이스 메서드를 통해 클래스에 구현 된 메서드의 값을 리턴합니다.

SeeAlso:

profile
BackEnd Developer

0개의 댓글