프로그래밍/웹관련

[TypeScript] 타입스크립트는 오버로드(Overload)를 어떻게 구현했는가?

당근천국 2023. 1. 10. 15:30

자바스크립트(JavaScript)는 엄밀하게 말하면 오버로드(Overload)가 없습니다.

 

타입스크립트(TypeScript)는 IDE에서나 타입스크립트지 출력되면 자바스크립트입니다.

이런 특성 때문에 오버로드도 자바스크립트의 특성을 따라가게 됩니다.

 

그렇다면 타입스크립트는 오버로드를 어떻게 구현하고 있을까요?

 

 

1. 자바스크립트의 오버로딩(Overloading)

자바스크립트는 오버로드가 없습니다.

 

"앵? 파라미터 다르게 해서 같은 이름으로 받는 거 쓸 수 있는데요?"

라는 말을 할 수 있겠지만 자바스크립트의 오버로드는 엄밀하게 말하면 오버로딩이 아니라

자바스크립트의 특성을 이용한 꼼수입니다.

 

아래 코드를 봅시다.

function Main()
{
	console.log("parameter 1 : " + Overloading("pA"));
	console.log("parameter 2 : " + Overloading("pA", "pB"));
	console.log("parameter 3 : " + Overloading("pA", "pB", "pC"));
	console.log("parameter 4 : " + Overloading("pA", "pB", "pC", "pD"));
}

function Overloading(a, b, c, d)
{
	let sReturn = "";

	if ("undefined" === typeof a)
	{//파라미터a 부터 없음
		sReturn += "없음";
	}
	else if ("undefined" === typeof b)
	{//파라미터b 부터 없음
		sReturn += a + "!";
	}
	else if ("undefined" === typeof c)
	{//파라미터c 부터 없음
		sReturn += a + " @ " + b;
	}
	else if ("undefined" === typeof d)
	{//파라미터b 부터 없음
		sReturn += a + " # " + b + " # " + c;
	}
	else
	{//파라미터 전부 있음
		sReturn += a + " $ " + b + " $ " + c + " $ " + d;
	}

	return sReturn;
}

 

이 코드 호출하는 'Main'의 내용을 보면 전형적인 오버로딩을 호출하는 방법을 쓰고 있습니다.

하지만 오버로딩이 구현된 'Overloading'을 보면 일반적인 오버로딩의 구현방식이 아니죠.

 

자바스크립트의 특성 중 하나인

'파라미터가 어떻게 되던 일단 지정된 함수는 호출하고 본다'

를 이용하여

1) 일단 함수를 호출하고 

2) 함수의 앞부분에서 전달된 파라미터의 상태를 확인한 후

3) 파라미터 상태(개수, 변수형)를 체크하고

4) 필요한 동작을 합니다.

 

이런 구현의 가장 큰 문제는 각 파라미터가 원하는 타입으로 들어왔는지 하나하나 체크해야 한다는 것입니다.

만약 2번째 파라미터가 1번 오버로드에서는 숫자, 2번 오버로드에서는 문자라면 

파라미터의 개수와 타입을 모두 체크해야 합니다.

 

이렇게 구현하다 보니 오버로딩의 개수가 늘어나면 기존 분기 코드와 안 맞아서 에러를 내는 경우가 생깁니다.

 

그래서 그냥 JSON(object)매개변수 하나로 데이터를 받고 파라미터에 따라 분기하는 방법도 많이 사용하는 것입니다.

이렇게 하면 최소한 파라미터의 이름이 명시되므로 파라미터가 뒤섞이므로 발생하는 문제는 해결되니까요.

 

 

2. 타입스크립트의 오버로딩

자바스크립트의 오버로딩 방식을 그대로 사용하면

1) 타입스크립트의 장점 중 하나인 IDE에서 자동완성도 안되고

2) 파라미터 개수가 맞지 않아 오류를 내게 됩니다.

 

그래서 타입스크립트에서는 서명이라는 것을 이용하게 됩니다.

 

2-1. 오버로드의 서명(Overload Signatures)

서명은 사용할 오버로드 함수 원형을 미리 선언하는 방식입니다.

런타임(여기서는 변환된 자바스크립트)에는 영향을 주지 않고 오로지 IDE를 위해 작성합니다.

 

 

예를 들면 아래와 같습니다.

//오버로드 서명
function Overloading(a: string): string;
function Overloading(a: string, b: string): string;
function Overloading(a: string, b: string, c: string): string;
function Overloading(a: string, b: string, c: string, d: string): string;

 

이렇게 선언하면 IDE 상에서 메소드가 오버로드 돼있음이 표시됩니다.

비주얼 스튜디오에서 오버로드된 함수가 표시된다.

 

 

2-2. 구현 서명(Implementation Signature)

오버로드 서명을 받아줄 구현 서명(Implementation Signature)을 작성해야 합니다.

이 구현 서명이 실제 오버로드 함수의 구현입니다.

//오버로드 서명
function Overloading(a: string): string;
function Overloading(a: string, b: string): string;
function Overloading(a: string, b: string, c: string): string;
function Overloading(a: string, b: string, c: string, d: string): string;

//오버로드 구현
function Overloading(a: any, b?: any, c?: any, d?: any): string
{
	let sReturn = "";

	return sReturn;
}

 

2번 ~ 5번 줄 : 오버로드용 서명입니다.

필요한 파라미터에 맞게 선언합니다.

 

 

8번줄 : 오버로드의 구현입니다.

모든 서명과 일치하도록 변수 타입과 파라미터 개수, 리턴 타입을 맞춰줍니다.

 

타입스크립트에서는 입력 여부가 불분명한 경우 파라미터 이름에 물음표(?)를 붙여서 해당 파라미터가 들어오지 않을 수 있음을 알려야 합니다.

물음표 다음에 나오는 파라미터들도 필수적으로 물음표를 붙어야 합니다.

앞의 파라미터가 없는데 뒤의 파라미터가 있을 수 없기 때문입니다.

 

 

2-3. 내부는 똑같다.

이제 함수를 구현하면 됩니다.

function Main()
{
	console.log("parameter 1 : " + Overloading("pA"));
	console.log("parameter 2 : " + Overloading("pA", "pB"));
	console.log("parameter 3 : " + Overloading("pA", "pB", "pC"));
	console.log("parameter 4 : " + Overloading("pA", "pB", "pC", "pD"));
}

//오버로드 서명
function Overloading(a: string): string;
function Overloading(a: string, b: string): string;
function Overloading(a: string, b: string, c: string): string;
function Overloading(a: string, b: string, c: string, d: string): string;

//오버로드 구현
function Overloading(a: any, b?: any, c?: any, d?: any): string
{
	let sReturn = "";

	if ("undefined" === typeof a)
	{//파라미터a 부터 없음
		sReturn += "없음";
	}
	else if ("undefined" === typeof b)
	{//파라미터b 부터 없음
		sReturn += a + "!";
	}
	else if ("undefined" === typeof c)
	{//파라미터c 부터 없음
		sReturn += a + " @ " + b;
	}
	else if ("undefined" === typeof d)
	{//파라미터b 부터 없음
		sReturn += a + " # " + b + " # " + c;
	}
	else
	{//파라미터 전부 있음
		sReturn += a + " $ " + b + " $ " + c + " $ " + d;
	}

	return sReturn;
}

 

자바스크립트와 똑같다....?

타입스크립트가 자동으로 함수를 나눠주는 게 아니기 때문에 처음에 말했던 것 처럼 구현은 자바스크립트를 따라갑니다.

 

이것은 변환된 자바스크립트를 보면 더 명확해집니다.

손으로 짠 자바스크립트와 거의 똑같죠.

 

작성해둔 오버로드 서명을 제거되고 구현만 남아 있습니다.

 

 

3. 오버로드가 꼭 필요한 건 아니다.

자바스크립트의 특성 때문에 오는 장점이 있는데 파라미터의 타입만 다르다면 굳이 서명을 만들어서 사용할 필요는 없습니다.

 

다음과 같은 오버로드가 있다고 생각해 봅시다.

//오버로드 서명
function Overloading2(a: string): string;
function Overloading2(a: number): string;

function Overloading2(a: string | number): string
{
	let s = "문자열 : ";
	let n = 10;

	if ("string" === typeof a)
	{
		s += a;
	}
	else
	{
		n += 1;
	}

	return "문자 : " + s + ", 숫자 : " + n;
}

 

5번 줄 : 구현에서 파라미터 타입을 'string | number'로 지정했으므로 따로 구현 서명을 작성하지 않아도 IDE에서 표시가 되긴 합니다. 

이런 경우 오버로드 서명을 작성하지 않아도 오버로드 처럼 동작하고 에러도 없으니 

따로 작성할 필요가 없죠.

 

물론 IDE에 표시되는 내용의 일관성을 위해 작성해도 상관없습니다.

 

 

마무리

참고 : TypeScript Docs - More on Functions - Function Overloads

 

결국 자바스크립트의 특성을 그대로 따라간다는 소립니다.

IDE에 표시되는 것만으로도 생산성이 올라가니 나쁘진 않은데.....

구현은 그대로 구현해야 하니 이게 뭐 하는 짓인가 싶네요....