타입스크립트로 복잡한 객체 지향 설계를 하다 보면, "거대 클래스"를 주입받아 다양한 하위 타입을 런타임에 구분해야 하는 상황이 자주 발생합니다. 이럴 때 안전하게 타입을 좁혀서(내로잉) 원하는 메서드나 속성을 사용하는 방법이 바로 덕타이핑(duck typing)과 사용자 정의 타입 가드(user-defined type guard)입니다.
class MegaService {
doSomething(): void { /* ... */ }
doAnotherThing(): void { /* ... */ }
isSpecial(): boolean { return false; }
}
class SpecialService extends MegaService {
override isSpecial(): boolean { return true; }
doSpecialThing(): void { /* ... */ }
}
class Consumer {
constructor(private service: MegaService) {}
process() {
// 여기서 타입 내로잉이 필요!
}
}
Consumer
는 MegaService 타입을 주입받지만, 실제로는 SpecialService 등 다양한 하위 타입이 들어올 수 있습니다. 이때 타입 내로잉이 필요합니다.
덕타이핑은 "오리처럼 걷고, 오리처럼 꽥꽥거린다면, 그건 오리다"라는 말에서 유래한 개념입니다. 즉, 객체의 실제 타입이 아니라, 특정 속성이나 메서드가 존재하는지로 타입을 판별하는 방식입니다.
process() {
if ('doSpecialThing' in this.service) {
// doSpecialThing 메서드가 있으면 SpecialService로 간주
(this.service as SpecialService).doSpecialThing();
} else {
this.service.doSomething();
}
}
이 방식은 상속 구조가 아니거나, 외부 라이브러리 등에서 타입 정보가 불명확할 때 유용합니다. 하지만 오타나 속성 이름 충돌 등으로 인한 런타임 오류 가능성이 있으니 주의해야 합니다.
타입스크립트에서는 사용자 정의 타입 가드를 통해 타입 내로잉을 더욱 안전하게 할 수 있습니다.
function isSpecialService(svc: MegaService): svc is SpecialService {
return 'doSpecialThing' in svc;
}
svc is SpecialService
를 명시하면,process() {
if (isSpecialService(this.service)) {
this.service.doSpecialThing(); // 타입 에러 없이 사용 가능!
} else {
this.service.doSomething();
}
}
이렇게 하면 코드의 가독성과 안전성이 높아지고, 여러 곳에서 재사용도 가능합니다.
instanceof
와 사용자 정의 타입 가드를 조합하는 것이 가장 안전합니다.실제 프로젝트에서 타입 내로잉이 필요한 상황이 있다면, 위의 패턴을 적극적으로 활용해 보세요!