--- id: wiki-2026-0508-replace-conditional-with-polymor title: Replace Conditional with Polymorphism (조건식을 다형성으로 바꾸기) category: 10_Wiki/Topics status: verified canonical_id: self aliases: [Replace Conditional with Polymorphism, RCwP, Polymorphism Refactoring] duplicate_of: none source_trust_level: A confidence_score: 0.9 verification_status: applied tags: [refactoring, oop, design-pattern, fowler] raw_sources: [] last_reinforced: 2026-05-10 github_commit: pending tech_stack: language: TypeScript framework: none --- # Replace Conditional with Polymorphism (조건식을 다형성으로 바꾸기) ## 매 한 줄 > **"매 type-discriminating switch/if-chain 을 매 polymorphic dispatch 로 교체"**. Fowler *Refactoring 2e* (2018) Ch.10. 매 type code 가 add 시마다 매 switch 들 모두 수정 필요한 shotgun surgery 의 표준 처방. 매 modern alternative — discriminated union + exhaustiveness, strategy map, pattern matching — 도 같은 본질을 공유한다. ## 매 핵심 ### 매 trigger smell - 매 동일 `switch (kind)` 가 codebase 여러 곳에 반복. - 매 새 type 추가가 매 N 곳의 switch 수정 강제. - 매 default branch 가 silently incorrect. ### 매 mechanics (Fowler) 1. 매 self-encapsulating type 추출 (subclass 또는 strategy). 2. 매 switch 한 case 를 method override 로 이동 (test 유지). 3. 매 모든 case 이동 후 매 base method 를 abstract 화. 4. 매 caller 에서 매 conditional 제거. ### 매 응용 1. Tax calculation: country-specific subclass. 2. Bird movement: Penguin/Parrot/Owl `getSpeed()` override. 3. Payment processor: Stripe/Toss/PayPal strategy. 4. AST visitor: node type 별 dispatch. ## 💻 패턴 ### Before: type-code switch ```typescript type Bird = { kind: 'penguin' | 'parrot' | 'owl'; voltage?: number; isNailed?: boolean; numberOfCoconuts?: number; }; function plumage(bird: Bird): string { switch (bird.kind) { case 'penguin': return 'average'; case 'parrot': return bird.isNailed ? 'tattered' : 'beautiful'; case 'owl': return 'attentive'; } } function airSpeed(bird: Bird): number { switch (bird.kind) { case 'penguin': return 50; case 'parrot': return 80 - 5 * (bird.voltage ?? 0); case 'owl': return 12 * (bird.numberOfCoconuts ?? 0); } } ``` ### After: polymorphism (classes) ```typescript abstract class Bird { abstract get plumage(): string; abstract get airSpeed(): number; } class Penguin extends Bird { get plumage() { return 'average'; } get airSpeed() { return 50; } } class Parrot extends Bird { constructor(private voltage = 0, private nailed = false) { super(); } get plumage() { return this.nailed ? 'tattered' : 'beautiful'; } get airSpeed() { return 80 - 5 * this.voltage; } } class Owl extends Bird { constructor(private coconuts = 0) { super(); } get plumage() { return 'attentive'; } get airSpeed() { return 12 * this.coconuts; } } ``` ### Modern alternative: discriminated union + exhaustiveness ```typescript type Bird = | { kind: 'penguin' } | { kind: 'parrot'; voltage: number; nailed: boolean } | { kind: 'owl'; coconuts: number }; function airSpeed(b: Bird): number { switch (b.kind) { case 'penguin': return 50; case 'parrot': return 80 - 5 * b.voltage; case 'owl': return 12 * b.coconuts; default: { const _exhaustive: never = b; return _exhaustive; } } } ``` ### Strategy map (data-driven dispatch) ```typescript const speedStrategies = { penguin: () => 50, parrot: (b: { voltage: number }) => 80 - 5 * b.voltage, owl: (b: { coconuts: number }) => 12 * b.coconuts, } as const; function airSpeed( kind: K, data: Parameters[0], ): number { return (speedStrategies[kind] as any)(data); } ``` ### Visitor (when ops > types) ```typescript interface Visitor { penguin(): R; parrot(b: { voltage: number; nailed: boolean }): R; owl(b: { coconuts: number }): R; } class SpeedVisitor implements Visitor { penguin() { return 50; } parrot(b: { voltage: number }) { return 80 - 5 * b.voltage; } owl(b: { coconuts: number }) { return 12 * b.coconuts; } } ``` ### Rust pattern matching (sister technique) ```rust enum Bird { Penguin, Parrot { voltage: u32, nailed: bool }, Owl { coconuts: u32 }, } impl Bird { fn air_speed(&self) -> u32 { match self { Bird::Penguin => 50, Bird::Parrot { voltage, .. } => 80 - 5 * voltage, Bird::Owl { coconuts } => 12 * coconuts, } } } ``` ## 매 결정 기준 | 상황 | Approach | |---|---| | OOP language, type 자주 add | Subclass polymorphism | | FP/TS, op 자주 add | Discriminated union + match (expression problem) | | 매 dispatch table 단순 | Strategy map (object literal) | | Op 많고 type 안정적 | Visitor | | Type 많고 op 안정적 | Sum type + match | | 매 single-use switch | 매 그대로 두기 — refactor cost > benefit | **기본값**: TS — discriminated union + exhaustive switch. OOP-heavy domain — subclass. ## 🔗 Graph - 부모: [[Refactoring_Best_Practices]] · [[Polymorphism]] - 변형: [[Visitor Pattern]] - 응용: [[Discriminated Union]] · [[Pattern Matching]] · [[Open-Closed Principle]] ## 🤖 LLM 활용 **언제**: 매 codebase 에 매 동일 switch 가 3+ 곳, 매 새 type 추가가 자주, 매 OCP 위반 가시. **언제 X**: 매 switch 1 곳, 매 type 안정적, 매 small dispatch — over-engineering. ## ❌ 안티패턴 - **Inheritance for everything**: 매 deep hierarchy — composition + interface 권장. - **Polymorphism without exhaustiveness**: 매 default branch 가 silent bug 숨김. - **Anemic subclass**: 매 method 1개 차이 — strategy 가 더 적합. - **Refactor without tests**: 매 behavior preservation 의 보증 없음. - **`instanceof` chain**: 매 polymorphism 의 antithesis — 매 새 switch 의 변종. ## 🧪 검증 / 중복 - Verified: Fowler *Refactoring 2e* (2018) Ch.10 "Replace Conditional with Polymorphism"; refactoring.com. - 신뢰도 A. ## 🕓 Changelog | 날짜 | 변경 | |---|---| | 2026-05-08 | Phase 1 | | 2026-05-10 | Manual cleanup — Fowler mechanics + modern union/strategy/visitor variants |