Replace Conditional With Polymorphism, 조건부 로직을 다형성으로 바꾸기를 알아보자.
요약

코드
switch (bird.type) {
case '유럽 제비':
return "보통이다";
case '아프리카 제비':
return (bird.numberOfCoconuts > 2) ? "지쳤다" : "보통이다";
case '노르웨이 파랑 앵무':
return (bird.voltage > 100) ? "그을렸다" : "예쁘다";
default:
return "알 수 없다";
}class EuropeanSwallow {
get plumage() {
return "보통이다";
}
}
class AfricanSwallow {
get plumage() {
return (this.numberOfCoconuts > 2) ? "지쳤다" : "보통이다";
}
}
class NorwegianBlueSwallow {
get plumage() {
return (this.voltage > 100) ? "그을렸다" : "예쁘다";
}
}배경
- 조건부 로직은 프로그램을 읽기 어렵게 만든다.
- 어떻게 하면 직관적으로 구조화할 수 있을까?
- 더 높은 수준의 개념을 도입하면 이를 분리시킬 수 있다.
- 클래스와 다형성이다.
- 각 타입이 자신만의 방식으로 처리하도록 구현하면 좋겠다.
절차
- 다형적 동작을 표현하는 클래스들이 없다면 하나 만든다. 기왕이면 인스턴스를 알아서 만들어 반환하는 팩토리 함수도 함께 만든다. (사용하는쪽에서 이게 더 편하다.)
- 호출하는 쪽에서 팩토리 함수를 사용하도록 수정한다.
- 조건부 로직을 일단 슈퍼 클래스로 옮긴다.
- 서브 클래스 중 하나를 선택한다. 조건부 로직 메서드를 오버라이드 한다.
- 같은 방식으로 다른 서브 클래스에도 적용한다.
- 슈퍼 클래스에는 기본 동작만 남긴다.
예시
function plumages(birds) {
return new Map(birds.map(b => [b.name, plumage(b)]));
}
function speeds(birds) {
return new Map(birds.map(b => [b.name, airSpeedVelocity(b)]));
}
function plumage(bird) {
switch (bird.type) {
case '유럽 제비':
return "보통이다";
case '아프리카 제비':
return (bird.numberOfCoconuts > 2) ? "지쳤다" : "보통이다";
case '노르웨이 파랑 앵무':
return (bird.voltage > 100) ? "그을렸다" : "예쁘다";
default:
return "알 수 없다";
}
}
function airSpeedVelocity(bird) {
switch (bird.type) {
case '유럽 제비':
return 35;
case '아프리카 제비':
return 40 - 2 * bird.numberOfCoconuts;
case '노르웨이 파랑 앵무':
return (bird.isNailed) ? 0 : 10 + bird.voltage / 10;
default:
return null;
}
}- 새의 종에 따른 비행 속도와 깃털 상태를 반환하는 함수이다.
- 새 종류에 따라 다르게 동작하는 함수가 몇 개 보인다.
function plumages(birds) {
return new Map(birds.map(b => [b.name, plumage(b)]));
}
function speeds(birds) {
return new Map(birds.map(b => [b.name, airSpeedVelocity(b)]));
}
function plumage(bird) {
new Bird(bird).plumage;
}
function airSpeedVelocity(bird) {
new Bird(bird).airSpeedVelocity;
}
class Bird {
constructor(birdObject) {
Object.assign(this, birdObject);
}
get plumage() {
switch (this.type) {
case '유럽 제비':
return "보통이다";
case '아프리카 제비':
return (this.numberOfCoconuts > 2) ? "지쳤다" : "보통이다";
case '노르웨이 파랑 앵무':
return (this.voltage > 100) ? "그을렸다" : "예쁘다";
default:
return "알 수 없다";
}
}
get airSpeedVelocity() {
switch (this.type) {
case '유럽 제비':
return 35;
case '아프리카 제비':
return 40 - 2 * this.numberOfCoconuts;
case '노르웨이 파랑 앵무':
return (this.isNailed) ? 0 : 10 + this.voltage / 10;
default:
return null;
}
}
}- 일단 슈퍼 클래스를 만들었고, 그 안에 서브클래스를 만들자.
- 아 팩토리 함수도 추가해주자.
function plumages(birds) {
return new Map(birds.map(b => [b.name, plumage(b)]));
}
function speeds(birds) {
return new Map(birds.map(b => [b.name, airSpeedVelocity(b)]));
}
function plumage(bird) {
createBird(bird).plumage;
}
function airSpeedVelocity(bird) {
createBird(bird).airSpeedVelocity;
}
function createBird(bird) {
switch (bird.type) {
case '유럽 제비':
return new EuropeanSwallow(bird);
case '아프리카 제비':
return new AfricanSwallow(bird);
case '노르웨이 파랑 앵무':
return new NorwegianBlueSwallow(bird);
default:
return new Bird(bird);
}
}
class Bird {
constructor(birdObject) {
Object.assign(this, birdObject);
}
get plumage() {
switch (this.type) {
case '유럽 제비':
return "보통이다";
case '아프리카 제비':
return (this.numberOfCoconuts > 2) ? "지쳤다" : "보통이다";
case '노르웨이 파랑 앵무':
return (this.voltage > 100) ? "그을렸다" : "예쁘다";
default:
return "알 수 없다";
}
}
get airSpeedVelocity() {
switch (this.type) {
case '유럽 제비':
return 35;
case '아프리카 제비':
return 40 - 2 * this.numberOfCoconuts;
case '노르웨이 파랑 앵무':
return (this.isNailed) ? 0 : 10 + this.voltage / 10;
default:
return null;
}
}
}
class EuropeanSwallow extends Bird {
}
class AfricanSwallow extends Bird {
}
class NorwegianBlueSwallow extends Bird {
}- 팩토리 함수를 하나 만들었고, 이걸 호출하도록 변경했다.
- 그리고 서브 클래스들을 일단 만들어봤다.
- 로직이 분리되지는 않았으니 이걸 처리해보자.
function plumages(birds) {
return new Map(birds.map(b => [b.name, plumage(b)]));
}
function speeds(birds) {
return new Map(birds.map(b => [b.name, airSpeedVelocity(b)]));
}
function plumage(bird) {
createBird(bird).plumage;
}
function airSpeedVelocity(bird) {
createBird(bird).airSpeedVelocity;
}
function createBird(bird) {
switch (bird.type) {
case '유럽 제비':
return new EuropeanSwallow(bird);
case '아프리카 제비':
return new AfricanSwallow(bird);
case '노르웨이 파랑 앵무':
return new NorwegianBlueSwallow(bird);
default:
return new Bird(bird);
}
}
class Bird {
constructor(birdObject) {
Object.assign(this, birdObject);
}
get plumage() {
return "알 수 없다";
}
get airSpeedVelocity() {
return null;
}
}
class EuropeanSwallow extends Bird {
get plumage() {
return "보통이다";
}
}
class AfricanSwallow extends Bird {
get plumage() {
return (this.numberOfCoconuts > 2) ? "지쳤다" : "보통이다";
}
}
class NorwegianBlueSwallow extends Bird {
get plumage() {
return (this.voltage > 100) ? "그을렸다" : "예쁘다";
}
}