본문 바로가기
토비의봄

1. 재사용성과 다이나믹 디스패치, 더블 디스패치

by 이석준석이 2021. 2. 20.

1. 메소드 디스패치란?

  • 어떤 메소드를 내가 호출한 것인가를 결정해서 실행하는 과정을 말한다.

2. 스태틱 디스패치

  • 프로그램이 실행되는 런타임 이전에도 어느 메소드가 호출될 것인지가 이미 결정된 경우를 말한다.

예시) main 함수에서 이미 어떤 함수를 호출할 지에 대해서 런타임 이전에도 다 알고있다.

public class Dispatch {
    static class Service {
        void run(int number) {
            System.out.println("run(" + number + ")");
        }

        void run(String msg) {
            System.out.println("run(" + msg + ")");
        }
    }

    public static void main(String[] args) {
        new Service().run(1);
        new Service().run("Dispatch");
    }
}

3. 런타임 디스패치

  • 런타임시점에 어떤 메소드를 호출할 것인지가 결정되는 경우를 말한다.
  • 아래의 코드에서 
    • svc.run(); 에서는 런타임 이전에는 어떤것이 실행될 것인지에 대한 정보가 없다.
    • 런타임 과정에서 receiver parameter 로 할당된 MyService1 이 넘어오며, MyService1 의 run() 이 호출되는 것
      • receiver parameter 은 간단하게 객체 내에서 사용하는 this 라고 생각하면 된다.
public class Dispatch {
    static abstract class Service {
        abstract void run();
    }

    static class MyService1 extends Service {
        @Override
        void run() {
            System.out.println("run1");
        }
    }

    static class MyService2 extends Service {
        @Override
        void run() {
            System.out.println("run2");
        }
    }

    public static void main(String[] args) {
        Service svc = new MyService1();

        svc.run();  // 런타임 시점에서 svc 에 할당되어있는 오브젝트가 무엇인지 본 뒤, 이에 의해서 결정된다.
                    // 메소드 호출과정에 receiver parameter (this 에 대한 정보) 가 들어있다.
                    // receiver parameter 를 통해 어떤 오브젝트를 실행할지 판단한 뒤에 수행한다.

        List<Service> svcList = Arrays.asList(new MyService1(), new MyService2());
        svcList.forEach(s -> s.run());  // 첫번째 수행에는 MyService1 에 대한 receiver parameter 인 this 가 [런타임] 에 넘어오고
                                        // 두번째 수행에는 MyService2 에 대한 receiver parameter 인 this 가 [런타임] 에 넘어오고
    }
}

4. 메소드 시그니처

  • 구성 :
    • 메소드 이름
    • 파라미터들의 타입
  • 위의 구성이 같은 메소드를 오버라이딩 할 수 있음
  • 위의 구성이 같은 메소드를 2개이상 정의할 수 없음
public class Dispatch {
    // 'hello(int)' is already defined in 'tv.toby.Dispatch'
    String hello(int id) { return ""; }
    List<String> hello(int id) { return null; }
}

5. 메소드 타입

  • 구성 :
    • 리턴 타입
    • 메소드 타입 파라미터 (타입)
    • 메소드 타입 아규먼트 (값)
    • exception
  • 위의 4가지가 일치하면 메소드 레퍼런스로 쓸 수 있음

아래의 코드에서

  • s.run 과 Service::run 
    • 리턴타입 : void
    • 파라미터 : 없음
    • 아규먼트 : 없음
    • exception : 없음
  • 메소드 레퍼런스로 사용
svcList.forEach(s -> s.run());
svcList.forEach(Service::run);

6. 더블 디스패치

 

현재 코드

  • SNS 에다가 Post 를 하는 코드

문제점

  • if문을 instanceof를 통해 분기하는 것
    • SNS 에 Instagram이 추가 (새로운 서비스) 되면 TextPost 부분에도 코드를 추가해야하며, PicturePost에도 코드를 추가해야 한다.
    • 에러 발생의 소지가 있다.
public class Dispatch {
    interface Post { void postOn(SNS sns); }
    static class TextPost implements Post {
        @Override
        public void postOn(SNS sns) {
            if(sns instanceof Facebook) {
                System.out.println("text -> facebook");
            }
            if(sns instanceof Twitter) {
                System.out.println("text -> twitter");
            }
        }
    }

    static class PicturePost implements Post {
        @Override
        public void postOn(SNS sns) {
            if(sns instanceof Facebook) {
                System.out.println("picture -> facebook");
            }
            if(sns instanceof Twitter) {
                System.out.println("picture -> twitter");
            }
        }
    }

    interface SNS {}
    static class Facebook implements SNS {}
    static class Twitter implements SNS {}
    
    public static void main(String[] args) {
        List<Post> posts = Arrays.asList(new TextPost(), new PicturePost());
        List<SNS> sns = Arrays.asList(new Facebook(), new Twitter());

        posts.forEach(p -> sns.forEach(s -> p.postOn(s)));
    }
}

개선 코드 1

  • 그러면 애초에 다른걸 추가하는 경우에 파라미터로 못받도록 컴파일에러로 잡아보자
    • 인터페이스로 파라미터를 받지말고 구현체로 파라미터를 받는 방식

문제점

  • posts.forEach(p -> sns.forEach(s -> p.postOn(s)));
  • 이 부분에서 s -> p.postOn(s) 가 동작하지 않는다.
    • s 의 타입이 SNS 이므로 위에 설명했던 두 호출의 메소드 타입이 다르다.
public class Dispatch {
    interface Post {
        void postOn(Facebook sns);
        void postOn(Twitter sns);
    }
    static class TextPost implements Post {
        @Override
        public void postOn(Facebook sns) {
            System.out.println("text -> facebook");
        }

        @Override
        public void postOn(Twitter sns) {
            System.out.println("text -> twitter");
        }
    }

    static class PicturePost implements Post {
        @Override
        public void postOn(Facebook sns) {
            System.out.println("picture -> facebook");
        }

        @Override
        public void postOn(Twitter sns) {
            System.out.println("picture -> twitter");
        }
    }

    interface SNS {}
    static class Facebook implements SNS {}
    static class Twitter implements SNS {}

    public static void main(String[] args) {
        List<Post> posts = Arrays.asList(new TextPost(), new PicturePost());
        List<SNS> sns = Arrays.asList(new Facebook(), new Twitter());

        posts.forEach(p -> sns.forEach(s -> p.postOn(s)));
    }
}

개선코드 2

  • SNS 쪽에 post 를 만든 뒤에 구현하도록 한다.

장점

  • 일단 위의 1번 개선코드에서 발생됐던, s -> p.postOn(s) 의 문제는 발생하지 않는다. 
    • 같은 메소드타입이니까
  • 새로운 SNS 가 추가되어도, POST 쪽의 코드는 손대지 않게된다.
    • 첫 문제의 코드에는 새로운 SNS가 추가하면 instanceof 로 분기하는 로직이 추가됐음 (휴먼에러 방지)
public class Dispatch {
    interface Post { void postOn(SNS sns); }
    static class TextPost implements Post {
        @Override
        public void postOn(SNS sns) {
            sns.post(this);
        }
    }

    static class PicturePost implements Post {
        @Override
        public void postOn(SNS sns) {
            sns.post(this);
        }
    }

    interface SNS {
        void post(TextPost post);
        void post(PicturePost post);
    }
    static class Facebook implements SNS {
        @Override
        public void post(TextPost post) {
            System.out.println("text -> facebook");
        }

        @Override
        public void post(PicturePost post) {
            System.out.println("picture -> facebook");
        }
    }
    static class Twitter implements SNS {
        @Override
        public void post(TextPost post) {
            System.out.println("text -> twitter");
        }

        @Override
        public void post(PicturePost post) {
            System.out.println("picture -> twitter");
        }
    }

    /**
     *  모든 포스트를 모든 sns 에 올리는 메소드라면
     */
    public static void main(String[] args) {
        List<Post> posts = Arrays.asList(new TextPost(), new PicturePost());
        List<SNS> sns = Arrays.asList(new Facebook(), new Twitter());

        posts.forEach(p -> sns.forEach(s -> p.postOn(s)));
    }
}

 

호출과정

  • s -> p.postOn(s)
    • p.postOn 의
      • p 는 뭘까?
    • TextPost 혹은 PicturePost 내부의 postOn 함수에서
      • sns.post(this) 의 sns 는 뭘까? 
  • 위에서 두번의 다이나믹 디스패칭 과정

그러면 새로운 Post 가 추가되면 SNS 를 건들게되는데?

  • 코드의 변화의 여부에따라 판한할 수 있도록

'토비의봄' 카테고리의 다른 글

4.2. Reactive Streams  (0) 2021.02.28
4.1. Reactive Streams  (0) 2021.02.28
3.2. Generics  (0) 2021.02.25
3.1. Generics  (0) 2021.02.24
2. 슈퍼 타입 토큰  (0) 2021.02.20