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 는 뭘까?
- p.postOn 의
- 위에서 두번의 다이나믹 디스패칭 과정
그러면 새로운 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 |