더블디스패치 패턴은 대체 왜 쓰이는 걸까?
자바는 싱글디스패치만을 지원한다고 한다.
그 말은 더블디스패치를 사용하려면 패턴으로 메꿔야 한다는 말인가보다.
예전에는 싱글디스패치만으로도 만족하고 살았지만 다른 많은 언어들이 실행시키는 인스턴스와 매개변수 둘 다 타입체크를 하여 멋져졌다 하니.
우리도 패턴으로 그것이 가능함을 보여야 하지 않겠나 해서 만들어 진 것이 아닌가 하는 상상을 해본다.
일단 만들어보자. 이제부터 할 일은 이렇게 된다.
인터페이스 둘을 만든다. (Format, Post)
인터페이스를 구현한다. (Post - Mail,SMS / Format - JPG,XML)
이 두 인터페이스를 연결한다.
실행해본다.
일단 왜 더블디스패치가 없이 만들면 어떻게 되는지 보자.
1. 인터페이스 둘을 만든다.
interface Format {}
interface Post {}
2. 인터페리스를 구현한다. (총 4개)
public class JPG implements Format { }
public class XML implements Format { }
public class Mail implements Post { }
public class SMS implements Post { }
이제 인터페이스에 필요한 내용들 추가하고 구현도 해서 이 두 인터페이스를 연결하자.
Post인터페이스는 JPG / XML에 따라 보내는 방식이 다를 수 있다. 그래서 그 타입에 따라 다른 일을 하도록하자.
public interface Post {
void send(Format format);
void send(JPG jpg);
void send(XML xml);
}
게다가 SMS/MAIL에 따라 다를 것이다.
public class Mail implements Post {
@Override
public void send(Format format) {
throw new RuntimeException("Mail.send(Format format) :: 타입을 알 수 없습니다.");
}
@Override
public void send(JPG jpg) {
System.out.println("Mail.send(JPG jpg)");
}
@Override
public void send(XML xml) {
System.out.println("Mail.send(XML xml)");
}
}
public class SMS implements Post {
@Override
public void send(Format format) {
throw new RuntimeException("SMS.send(Format format) :: 타입을 알 수 없습니다.");
}
@Override
public void send(JPG jpg) {
System.out.println("SMS.send(JPG jpg)");
}
@Override
public void send(XML xml) {
System.out.println("SMS.send(XML xml)");
}
}
다 되었다. 실행해보자.
public class Test {
public static void main(String[] args) {
Post post = new Mail();
Format format = new JPG();
post.send(new JPG());
post.send(format);
}
}
잘 될까?
Mail.send(JPG jpg)
Exception in thread "main" java.lang.RuntimeException: Mail.send(Format format) :: 타입을 알 수 없습니다.
at designpattern.doubledispatch.post.Mail.send(Mail.java:11)
at designpattern.doubledispatch.Test.main(Test.java:13)
하나는 잘 되었고, 다른 하나는 잘 되지 않았다. 왜냐하면 메소드 오버로딩은 정적 디스패치(static dispatch)를 한다. 이 말은 런타임에 타입을 동적으로 체크하는 것이 아니라 미리 컴파일 이전에 타입을 미리 제대로 정해 놔야 한다는 말이다.
에러가 난 곳은 정확한 타입(JPG)를 넣은 것이 아니라. Format이라는 인터페이스를 넣었다. 이것이 문제가 되는 것
그러면 어떻게 해야 할까?
일단 자바에서는 매개변수 타입체크를 동적으로 하지않는다고 하니까 타입체크를 두 번 하도록 해보자.
지금부터 할일은
- Post 인터페이스를 매개변수가 Format인 것만 남기고 삭제한다. (어짜피 이걸로 타입체크를 못함)
- Format 인터페이스에 Mail,SMS(Post의 구현체 전부)를 각각 매개변수로 하여 void send(T t)형식의 메소드를 추가한다
- Post의 구현체에서 해당 메소드를 호출한다.
- 실제로 로직을 실행하는 곳은 Format의 구현체들에서 각각 구현한다.
말이 잘 전달되지 않은 것 같은데 일단 코드를 한 번 짜보자.
// 1. 매개변수가 Format 인것 빼고 다 삭제
public interface Post {
void send(Format format);
}
// 2. Mail,SMS를 각각 매개변수로 send라는 메소드를 추가
public interface Format {
/*
* 여기에서 타입체크를 하는 것이다. 원래는 Post에서 했지만
* 한 번 더 하기 위해, Format에서 한다.
*
*/
void send(Mail mail);
void send(SMS sms);
}
이제 인터페이스를 바꿨으니 구현체들을 바꿔보자.
// 3. 새로 만든 메소드 호출
public class Mail implements Post {
@Override
public void send(Format format) {
format.send(this); // 여기서 이미 this는 타입체킹이 되어있음.
}
}
public class SMS implements Post {
@Override
public void send(Format format) {
format.send(this);
}
}
이제 실제 로직이 들어있는 곳을 보자.
// 4. 실제 로직이 있는 곳 변경
public class JPG implements Format {
@Override
public void send(Mail mail) {
System.out.println("mail send JPG");
}
@Override
public void send(SMS sms) {
System.out.println("SMS send JPG");
}
}
public class XML implements Format {
@Override
public void send(Mail mail) {
System.out.println("mail send XML");
}
@Override
public void send(SMS sms) {
System.out.println("SMS send XML");
}
}
한번 실행해보자.
public class Test {
public static void main(String[] args) {
Post post = new Mail();
Format format = new JPG();
post.send(new JPG());
post.send(format);
}
}
과연 결과는??
mail send JPG
mail send JPG
성공이다. 이 패턴은 비지터 패턴과 아주 유사하다고 하는데, 비지터 패턴은 내 생각에는 클래스를 변경하지 않고 새로운 로직을 추가하기 위해 필요한 패턴인 것 같고
더블디스패치패턴은 좀 더 일반적인 기술같다. 양쪽의 타입을 제대로 체크하기 위해 만든 기술인듯하다.