경주장
6주차 과제: 상속 #6 (~02.29) - 메소드 디스패치 본문
학습할 것 (필수)
자바 상속의 특징메소드 오버라이딩final 키워드super 키워드다이나믹 메소드 디스패치 (Dynamic Method Dispatch)- 추상 클래스
- Object 클래스
메소드 디스패치
Def)
Method Dispatch is the mechanism that helps to decide which operation should be executed, or more specifically, which method implementation should be used.
아래의 예제는 토비의 봄 TV 1화를 보고 정리한 내용입니다.
Static Dispatch
/**
* Static Dispatch
*/
public class DispatchV1 {
static class Service {
void run(int number) {
System.out.println("Service.run("+number+")");
}
void run(String msg){
System.out.println("Service.run("+msg+")");
}
}
public static void main(String[] args) {
new Service().run(1);
new Service().run("Dispatch");
}
}
이 경우 컴파일러는
new Service().run(1); 이 첫번째 run 메소드를 호출하고
new Service().run("Dispatch")가 두번째 run 메소드를 호출하는 것을 알고 있다.
컴파일러가 알고 있다는 뜻은 컴파일된 바이트코드에도 반영이 되어 있다는 의미이다.
컴파일타임에 이미 Dispatch가 완료되었으므로 런타임에 아무런 고민 없이 메소드를 호출 할 수 있다.
Dynamic Dispatch
/**
* Dynamic Dispatch
*/
public class DispatchV2 {
static abstract class Service {
abstract void run();
}
static class MyService1 extends Service {
@Override
void run() {
System.out.println("MyService1.run");
}
}
static class MyService2 extends Service {
@Override
void run() {
System.out.println("MyService2.run");
}
}
public static void main(String[] args) {
Service svc = new MyService1();
svc.run();
}
}
컴파일러는 svc.run( );이 MyService1의 run인지 MyService2의 run인지 compile time에 알 수 없다. svc는 MyService1으로 선언되어있지만 run을 호출하기전 바꿔치기가 되었을 수도 있고 아무튼 결정할 수가 없다.
따라서 런타임에 자바는 svc에 계속 추적하는 this에 해당하는 "receiver parameter"( dot(.) 앞에 있는 녀석) 라는 숨겨진 파라미터에 의해서 호출할 run 메소드를 결정한다. 이를 Dynamic Dispatch라고 한다.
Double Dispatch (Multiple Dispatch)
다이나믹 디스패치가 두번 적용된 경우인 Double Dispatch를 살펴보겠습니다.
토비님 말씀으로는 10년에 한번정도 사용하는 기법이라고 합니다.
두가지 이상의 오브젝트 하이라키에서 타입들의 조합으로 1차원적인 어떤 비즈니스 로직구조가 만들어 지는 경우 활용할 수 있습니다.
/**
* Double Dispatch 적용 X
*/
public class DispatchV3 {
interface Post { void postOn(SNS sns);}
static class Text implements Post{
public void postOn(SNS sns) {
System.out.println("text -> " + sns.getClass().getSimpleName());
}
}
static class Picture implements Post{
public void postOn(SNS sns) {
System.out.println("picture -> " + sns.getClass().getSimpleName());
}
}
interface SNS { }
static class Facebook implements SNS{ }
static class Twitter implements SNS{ }
public static void main(String[] args) {
List<Post> posts = Arrays.asList(new Text(), new Picture());
List<SNS> sns = Arrays.asList(new Facebook(), new Twitter());
posts.forEach(p -> sns.forEach(p::postOn));
}
}
실행 결과
text -> Facebook
text -> Twitter
picture -> Facebook
picture -> Twitter
SNS와 Post가 모두 다형성을 활용하여 구현 되어있습니다. 현재는 Post의 타입에 따라 구현하고자 하는 각 SNS에 대한 postOn의 로직이 간단하여 클래스 내임을 추출하는 방식으로 구현 할 수 있습니다. 하지만 Post가 SNS의 타입에 따라 호출하고자 하는 로직이 복잡한 경우는 어떻게 하는것이 좋을까요?
가장 쉽게 생각할 수 있는 방식은 postOn메소드에서 sns의 타입에 따라 if 문으로 로직을 구분하는것 입니다.
/**
* Double Dispatch 적용 X
*/
public class DispatchV3 {
interface Post { void postOn(SNS sns);}
static class Text implements Post{
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 Picture implements Post{
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 Text(), new Picture());
List<SNS> sns = Arrays.asList(new Facebook(), new Twitter());
posts.forEach(p -> sns.forEach(p::postOn));
}
}
실행 결과
text -> Facebook
text -> Twitter
picture -> Facebook
picture -> Twitter
현재는 로직이 간단하지만 매우 거대한 로직이 postOn 메소드에 숨어있다고 생각해주세요. if 문을 통해 간단하게 같은 결과를 만들어 냈습니다. 하지만 이는 매력적인 방법이 아닙니다. SNS의 구현체로 GooglePlus가 추가되는 상황을 고려해보면 우리는 text와 picture의 postOn메소드에 if 문을 추가로 작성해야 할 것입니다. 이는 실수하기 쉬운 코드구조이며 정확하게는 객체지향의 OCP를 위반한 것입니다.
바람직하지 않은 위의 방법을 대체할 다른 방법에는 아래와 같은 시도가 있을 수 있습니다.
/**
* Double Dispatch 적용 X
*/
public class DispatchV3 {
interface Post {
void postOn(Facebook sns);
void postOn(Twitter sns);
}
static class Text implements Post{
public void postOn(Facebook sns) {
System.out.println("text -> Facebook");
}
public void postOn(Twitter sns) {
System.out.println("text -> Twitter");
}
}
static class Picture implements Post{
public void postOn(Facebook sns) {
System.out.println("picture -> Facebook");
}
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 Text(), new Picture());
List<SNS> sns = Arrays.asList(new Facebook(), new Twitter());
posts.forEach(p -> sns.forEach(p::postOn)); //컴파일 에러
}
}
Post의 인터페이스에서 다형성을 제거하고 클래스를 인자로 받는 두 postOn메소드를 오버로딩하였습니다. 덕분에 postOn메소드의 구현에 지저분한 if문을 제거하고 4가지 조합에 따른 로직을 개별적으로 구현 할 수 있었습니다.
하지만 main 메소드에서 컴파일 에러가 발생합니다. 루프의 p 즉 Post 인터페이스에 해당하는 postOn메소드가 SNS에 정의되어 있지 않기 때문입니다.
더블 디스패치 기법을 활용하여 위의 문제를 해결하는 코드는 아래와 같습니다.
/**
* Double Dispatch 적용
*/
public class DispatchV3 {
static 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 text);
void post(Picture picture);
}
static class Facebook implements SNS{
public void post(Text text) {
System.out.println("text -> Facebook");
}
public void post(Picture picture) {
System.out.println("picture -> Facebook");
}
}
static class Twitter implements SNS{
public void post(Text text) {
System.out.println("text -> Twitter");
}
public void post(Picture picture) {
System.out.println("picture -> Twitter");
}
}
public static void main(String[] args) {
List<Post> posts = Arrays.asList(new Text(), new Picture());
List<SNS> sns = Arrays.asList(new Facebook(), new Twitter());
posts.forEach(p -> sns.forEach(p::postOn)); //디스패치 한번
}
}
첫번째 디스패치에서
컴파일러는 s 즉 sns의 타입에 따라 실행할 postOn의 구현을 두개중에 하나 고릅니다.
두번째 디스패치에서
컴파일러는 this 즉 post의 타입에 따라 실행할 post의 구현을 네개중에 하나 고릅니다.
위와 같은 구현으로 조합에 따른 로직의 분리를 if문 없이 달성하였으며 OCP를 충족하는 구조를 얻을 수 있었습니다.
@)출처
그리고
'JAVA > whiteship_live-study' 카테고리의 다른 글
6주차 과제: 상속 #6 (~02.29) - Object 클래스 (0) | 2022.02.23 |
---|---|
6주차 과제: 상속 #6 (~02.29) - 추상 클래스 (0) | 2022.02.23 |
6주차 과제: 상속 #6 (~02.29) - final, super 키워드 (0) | 2022.02.23 |
6주차 과제: 상속 #6 (~02.29) - 메소드 오버라이딩 (0) | 2022.02.22 |
6주차 과제: 상속 #6 (~02.29) - 자바 상속의 특징 (0) | 2022.02.22 |