다이나믹 메서드 디스패치는 어떤 메서드를 호출할 지 결정하여 실제로 실행시키는 과정을 말한다. 이런 메서드 디스패치에는 정적 메서드 디스패치(Static Method dispatch), 동적 메서드 디스패치(Dynamic Method Dispatch), 더블 디스패치(Double Dispatch) 세가지가 존재한다.
어떤 메서드를 호출할 지 결정하여 실제로 실행시키는 과정이란 무엇을 말하는 것일까? 아래의 코드를 살펴 보자. 아래에서 알 수 있는 것은 main()에서 b.print()를 실행하면 B.class 내부에 override 된 함수가 불릴 것을 알 수 있다. 위와 같은 경우를 전반적으로 아우르는 말이 메서드 디스패치이다. 이렇듯 호출에 대한 실행을 알고 있다고 정의할 수 있다. 하지만 아래의 코드는 정적 메서드 디스패치이다. 왜냐하면 이미 컴파일이 되는 시점부터 B.class의 print()를 사용할 것이라는게 명시되어 있다. 그렇다면 동적은 무엇일까? 살짝만 비틀면 된다. 컴파일 런타임 도중에 결정 되는 것이 아닌 런타임 도중에 확인되는 메커니즘이다.
public class Test {
public static void main(String[] args) {
B b = new B();
b.print();
}
}
class A{
public void print(){
System.out.println("A class Method print()");
}
}
class B extends A{
public void print(){
System.out.println("B class Method print()");
}
}
Java
복사
정적 메서드 디스패치
다이나믹 메서드 디스패치
그럼 동적 메서드 디스패치는 런타임 도중에 결정 되는 것이라고 했다. 무슨 말일까? 다시 말하면 컴파일 도중에 실행이 결정되는 것이 아니다. 즉, 컴파일러도 모른다. 다음 코드를 보면 조금 이해가 된다. 주석으로 실행 표시가 되어 있는 곳은 런타임 도중에 결정된다. 즉, Printer내부의 Consumer<String>은 뭘 사용하게 될지 조상님도 모른다. 오직 런타임 도중에 들어오는 payment 변수에 따라 결정된다.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.function.Consumer;
public class Test {
private static Consumer<String> cashOrderPrint = s -> System.out.println("현금 : 주문은 " + s + " 입니다.");
private static Consumer<String> cardOrderPrint = s -> System.out.println("카드 : 주문은 " + s + " 입니다.");
public static void main(String[] args) throws IOException {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))){
String order = reader.readLine();
String payment = reader.readLine();
System.out.println();
Printer verify = verify(payment);
verify.print(order); // 실행
}
}
public static Printer verify(String payment){
switch (payment){
case "cash":
return new Printer(cashOrderPrint);
case "card":
return new Printer(cardOrderPrint);
default:
throw new UnsupportedOperationException("지원하지 않는 주문 형식입니다.");
}
}
}
class Printer{
private Consumer<String> printing;
public Printer(Consumer<String> printing) {
this.printing = printing;
}
public void print(String str){
printing.accept(str);
}
}
CONSOLE RESULT
햄버거 // 입력 값 order, payment
cash
현금 : 주문은 햄버거 입니다. // result
Java
복사
다이나믹 메서드 디스패치
더블 메서드 디스패치
그럼 더블 메서드 디스패치는 무엇일까? 더블? 더블에서 의미를 찾을 수 있다. 다이나믹 메서드 디스패치에서는 조상님도 모르는 실행을 런타임 도중에 결정하여 실행하는 것이라고 했다. 더블이란 말처럼 이 과정을 두번 반복하는 것을 더블 메서드 디스패치라고 한다. 간단한 예제가 있다.
더블 메서드 디스패치
위의 예제에서 알 수 있듯 Sns, Post 가 이미 자신이 사용할 post(Text, Picture)를 알고 있는 상태가 아니다. 또한 Sns(Twitter, Facebook)를 알고 있는 상태가 아니다. 그런데 우리가 주목해야 할 것은 알고 있냐 아니냐가 아니다. 왜 이런 패턴이 생겨났는가?에 대해서 좀 더 주목해야 할 것 같다.
이 이전에 만약 메서드 디스패치가 없다고 생각해보자! 그럼 어떨까? 답은 간단하게 해결할 수 있다. if-else, switch-case, instanceof 의 조합으로 하나하나 맞는 SNS, Post를 찾아 그에 맞게 형변한을 한 이후 실행할 메서드를 실행해주는 코드를 만들어주면 된다. 그렇다면 과연 이 로직은 어디에 포함되어야하는가? 어디에도 포함되기 애매한 내용이기 때문에 간단하게 CheckSns, CheckPost 등의 클래스를 작성하고 이를 이용하여 객체를 받아사용하는 형태로 만들 것 같다. 이처럼 간단히 생각해보아도 메서드 디스패치가 존재하지 않았다면 간단히 해결할 수 있는 일도 사서 에러를 만드는 코드를 덕지덕지 만들어야 한다. 더 큰 문제는 내 코드에 SNS의 종류가 추가되는 날이면 분기문, CheckSns, CheckPost 등의 모든 로직에 추가한 sns의 분기를 만들어야 한다. 그래서 이러한 더블 디스패치와 같은 기법들이 좋은 평가를 받는다.
아래의 “템플릿 어떻게 만들어?”에는 예전에 내가 작성한 전략 패턴을 활용한 엑셀라이브러리(POI) 커스텀이 있다. 간단하게 설명하자면 POI에서 Cell의 종류에 따라 가져오는 메서드가 다르다. 그 덕분에 실제 엑셀을 사용하는 사람과 프로그램 사이에 간극이 발생한다. 예를 들자면 실제 엑셀 사용자의 40%(실제로 경험한 일)은 숫자가 다 숫자로만 나오는 줄 알고 있다. 그 덕분에 숫자를 문자형식으로 입력해버리면 프로그램은 못알아 먹고 Format Exception을 띄우고 죽어버린다. 그 덕분에 나는 분기문으로 덕지덕지 코딩이 된 엑셀 파일입출력 프로그램을 만들 수 있었다. 하지만 그게 싫었던 나는 이걸 좀 더 이쁘게? 만들 방법이 없는가 고민하다가 이번 주제와 유사한 방식을 사용하여 메서드 디스패치를 사용했다. 결론적으로 전략패턴을 많이 활용했지만! 그래서 결론적으로 타입에 따라 자동으로 메서드를 호출해주는 방식을 만들 수 있었다. 다음의 글에서 좋은 점들은 추가 변경 사항까지 고려하여 모조리 테스트했다. 이미 추가 변경을 고려하고 만들려고 했고 실제 내 코드의 변화를 관찰했다. 덕분에 OCP 원칙은 잘 지킬 수(?) 있었던 것 같다. 한번 봐주면 좋을 것 같다.