21:59 Method Dispatch? 어떤 메서드를 내가 호출할 것인지 결정해서, 실제로 그것을 실행하는 과정을 의미. Static Dispatch / Dynamic Dispatch가 존재한다.
public class Dispatch { static class Service { void run(int number) { System.out.println("run(" + number + ")"); } void run(string str) { System.out.println("run(" + str + ")"); } } public static void main(String[] args) { new Service().run(1); new Service().run("str"); }}
run(1)run(str)
run()이라는 메서드는 컴파일 시점에 이 메서드가 실행될 지를 컴파일러가 알고 있다. (Static Dispatch)
이름이 갖고 인자가 다른 오버로딩된 메서드의 경우에도 별도의 메서드로 취급되기에 Static Dispatch이다.
Static Dispatch: 컴파일되는 시점에 정확하게 어떤 메서드가 호출될 지 정적으로 결정되는 것
Class를 Static으로 정의하는 이유는 지금 이 메모의 주제인 Static Dispatch와는 전혀 관계없는 이야기이다.
클래스 안에 클래스를 완전히 독립적으로, 별개로 정의하려면 Static으로 만들어야 한다.
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) { MyService1 svc = new MyService(); svc.run(); MyService2 svc = new MyService(); svc2.run(); }}
run1run2
이것도 마찬가지로 Static Dispatch. 하지만 Service 타입으로 받게 되면…?
Service svc = new MyService1();svc.run();
이 경우에는 svc.run(); 시점에서 같은 이름으로 정의되어 있는 두 개의 메서드 중 어떤 것을 실행할 것인지 컴파일 시점에 결정되어 있지 않다.
Runtime 시점에 svc에 할당되어 있는 객체가 무엇인지 확인하고 그것에 의해 결정되는 것!
메서드 호출과정에 첫번째로 들어가있는 것 중에 하나가 Receiver Parameter
this에 해당하는 객체가 들어가 있다.
이것에 기반하여 호출할 메서드가 결정된다.
List<Service> svcs = Arrays.asList(new MyService1(), new MyService2());svcs.forEach(service -> service.run()); // Receiver Parameter가 동적으로 결정되기 때문에 매 번 다르다.// svcs.forEach(Service::run);
지금은 로직이 단순하지만, 4가지 조합에 따라서 각각 비즈니스 로직을 다르게 구현해야 할 때가 있다…!
그런 경우에 각각을 구분해서 구현하려면? 가장 쉽게 떠올리는 방법은 instanceof로 매 번 코드에서 타입을 체크하는 것
static class Text implements Post { public void postOn(SNS sns) { if (sns instanceof Facebook) { // ... } if (sns instanceof Twitter) { // ... } }}static class Picture implements Post { public void postOn(SNS sns) { if (sns instanceof Facebook) { // ... } if (sns instanceof Twitter) { // ... } }}
타입을 판별하기 위해 IF 문을 사용하고 있다? 문제가 발생할 수 있다.
예를 들어 Google이 추가되는 경우 실제 분기를 postOn()에 추가하지 않아도 코드 동작에 전혀 이상이 없다.
이상이 없다는 점이 문제…!
새로운 타입이 추가될 때마다 IF 문을 추가해주어야 하고, 깜빡할 가능성이 높다.
instanceof라는 것은 안티패턴으로도 많이 지적이 된다.
객체지향스럽게 접근하려면?
postOn에서 타입을 애초에 구분해서 인터페이스를 정의한다. →COMPILE ERROR
interface Post { void postOn(Facebook sns); void postOn(Twitter sns);}static class Text implements Post { public void postOn(Facebook sns) { // ... } public void postOn(Twitter sns) { // ... }}
List<Post> posts = Arrays.asList(new Text();, new Picture());List<SNS> sns = Arrays.asList(new Facebook(), new Twitter());posts.forEach(p -> sns.forEach(s->p.postOn(s)));// Cannot resolve method 'postOn(SNS)'
메서드 오버로딩은 Static Dispatch에 의한 것이기 때문에, 컴파일하는 시점에 파라미터의 타입을 정확히 체크해서 메서드를 정해놓아야 한다.
하지만 위 코드의 경우 파라미터가 Facebook / Twitter로 구체화되어있는데, 사실 자바가 보기에는 SNS 타입이어야만 한다. 반복문의 대상이 List<SNS>이므로…
1:02:08 그럼 어떻게 풀어내야 할까? 이런 문제에 대해서 고민했던 논문이 A Simple Technique for Handling Multiple Polymorphism
이중 다형성의 적용이 필요하다. 폴리모피즘에 해당하는 타입을 런타임 시점에 결정하는 것을 Parameter를 가지고 하려고 했던 점이 문제이다.
자바는 Dynamic Dispatch을 파라미터를 기준으로 하지 않는다.
두 가지 종류의 타입들의 조합에 해당하는 비즈니스 로직을 두 번째 타입을 결정해야 하는 SNS 쪽으로 옮겨놓아보자.
interface Post { void postOn(SNS sns); }static class Text implements Post { public void postOn(SNS sns) { sns.post(this); }}static class Picture implements Post { public void postOn(SNS sns) { sns.post(this); }}interface SNS { void post(Text post); void post(Picture post);}static class Facebook implements SNS { public void post(Text post) { System.out.println("text-facebook"); } public void post(Picture post) { System.out.println("picture-facebook"); }};static class Twitter implements SNS { public void post(Text post) { System.out.println("text-twitter"); } public void post(Picture post) { System.out.println("picture-twitter"); }};
두번째 폴리모피즘을 적용할 SNS 쪽에 옮겨놓고, Post 쪽에서는 전달받은 sns.post() 를 호출하면서 this, 즉 Post의 객체정보를 전달한다.
이것이 Double Dispatch인데, instanceof를 사용하는 것에 비해 얼마나 대단한 의미가 있나?
Google이 추가되는 경우, SNS을 구현하는 Google 클래스를 정의하더라도 Post 관련 클래스에는 전혀 손을 댄 것이 없다.
Post 타입은 SNS 타입에 의존하고 있는데, SNS 타입의 일부가 변경(새 타입 추가)되었지만 영향받지 않았다.