2017년 4월 23일 일요일

[java][design pattern] 더블 디스패치 패턴은 언제 쓰일까?

더블디스패치 패턴은 대체 왜 쓰이는 걸까?
자바는 싱글디스패치만을 지원한다고 한다.
그 말은 더블디스패치를 사용하려면 패턴으로 메꿔야 한다는 말인가보다.
예전에는 싱글디스패치만으로도 만족하고 살았지만 다른 많은 언어들이 실행시키는 인스턴스와 매개변수 둘 다 타입체크를 하여 멋져졌다 하니.
우리도 패턴으로 그것이 가능함을 보여야 하지 않겠나 해서 만들어 진 것이 아닌가 하는 상상을 해본다.

일단 만들어보자. 이제부터 할 일은 이렇게 된다.
  • 인터페이스 둘을 만든다. (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이라는 인터페이스를 넣었다. 이것이 문제가 되는 것
    그러면 어떻게 해야 할까?
    일단 자바에서는 매개변수 타입체크를 동적으로 하지않는다고 하니까 타입체크를 두 번 하도록 해보자.
    지금부터 할일은
    1. Post 인터페이스를 매개변수가 Format인 것만 남기고 삭제한다. (어짜피 이걸로 타입체크를 못함)
    2. Format 인터페이스에 Mail,SMS(Post의 구현체 전부)를 각각 매개변수로 하여 void send(T t)형식의 메소드를 추가한다
    3. Post의 구현체에서 해당 메소드를 호출한다.
    4. 실제로 로직을 실행하는 곳은 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
    
    성공이다. 이 패턴은 비지터 패턴과 아주 유사하다고 하는데, 비지터 패턴은 내 생각에는 클래스를 변경하지 않고 새로운 로직을 추가하기 위해 필요한 패턴인 것 같고
    더블디스패치패턴은 좀 더 일반적인 기술같다. 양쪽의 타입을 제대로 체크하기 위해 만든 기술인듯하다.

    댓글 없음:

    댓글 쓰기