☕️ Java/기본

[Java] Double Dispatch란? (feat. Visitor Pattern)

말 랑 2022. 7. 8. 23:45
728x90

 

 


Double Dispatch를 학습하기 전 Method Dispatch가 무엇인지 잘 모르신다면 아래 글을 참고해주세요.

 

https://ttl-blog.tistory.com/776

 

[Java] Method Dispatch란? (Static, Dynamic)

Method Dispatch 어떤 메소드를 호출할 것인가를 결정하고 실행하는 과정을 의미합니다. Method Dispatch의 종류에는 Static Method Dispatch와 Dynamic Method Dispatch가 있습니다. Static Method Dispatch 컴파..

ttl-blog.tistory.com

 

 

 

 

 

 

 

 

 

 

 

Double Dispatch

Dynamic Method Dispatch를 2번 한다는 의미입니다.

Dynamic Method Dispatch를 2번 한다는 것은,

실행 시점에 호출할 메서드결정하고 실행하는 과정을 2번에 걸쳐서 진행한다는 의미입니다.

 

 

코드를 통해 자세히 살펴보겠습니다.

 

import java.util.List;

public class Dispatch {
	interface Post {
		void postOn(SNS sns);
	}

	static class Text implements Post {
		@Override
		public void postOn(SNS sns) {
			System.out.println("Text PostOn [" + sns.getClass().getSimpleName() +"]");
		}
	}

	static class Picture implements Post {
		@Override
		public void postOn(SNS sns) {
			System.out.println("Picture PostOn [" + sns.getClass().getSimpleName() +"]");
		}
	}

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

	public static void main(String[] args) {
		List<Post> posts = List.of(new Text(), new Picture());
		List<SNS> snss = List.of(new Facebook(), new Instagram());

		posts.forEach(post -> snss.forEach(post::postOn));
	}
}

 

두 개의 인터페이스 Post와 SNS를 정의하였습니다.

Post는 postOn 메서드를 통해 SNS에 Post를 게시합니다.

 

위 코드의 실행 결과는 다음과 같습니다.

 

 

위 경우에는 아무런 문제가 발생하지 않습니다.

 

 

 

 

 

 

 

 

이제 위 코드에 postOn으로 들어오는 SNS의 종류에 따라 다른 작업을 실행해 보도록 하겠습니다.

import java.util.List;
import java.util.stream.IntStream;

/**
 * Created by ShinD on 2022/07/08.
 */
public class Dispatch {
	interface Post {
		void postOn(SNS sns);
	}

	static class Text implements Post {
		@Override
		public void postOn(SNS sns) {
			if (sns instanceof Facebook) {
				System.out.println("Text postOn [Facebook]");
			}
			if (sns instanceof Instagram) {
				System.out.println("Text postOn [Instagram]");
			}
		}
	}

	static class Picture implements Post {
		@Override
		public void postOn(SNS sns) {
			if (sns instanceof Facebook) {
				System.out.println("Picture postOn [Facebook]");
			}
			if (sns instanceof Instagram) {
				System.out.println("Picture postOn [Instagram]");
			}
		}
	}

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

	public static void main(String[] args) {
		List<Post> posts = List.of(new Text(), new Picture());
		List<SNS> snss = List.of(new Facebook(), new Instagram());

		posts.forEach(post -> snss.forEach(post::postOn));
	}
}

위 코드 역시 결과를 출력하는 것에는 문제가 없습니다.

 

그러나 이는 SNS의 새로운 구현체가 등장하였을 경우 문제가 발생합니다.

 

 

 

 

 

 

SNS에 Twitte를 추가하고, main에서의 SNS 리스트에 Twiter를 추가해 보도록 하겠습니다.

import java.util.List;
import java.util.stream.IntStream;

/**
 * Created by ShinD on 2022/07/08.
 */
public class Dispatch {
	
    //... 동일
    
	interface SNS {}
	static class Facebook implements SNS {}
	static class Instagram implements SNS {}
	static class Twitter implements SNS {}

	public static void main(String[] args) {
		List<Post> posts = List.of(new Text(), new Picture());
		List<SNS> snss = List.of(new Facebook(), new Instagram(), new Twitter());

		posts.forEach(post -> snss.forEach(post::postOn));
	}
}

 

위 코드의 실행 결과는 다음과 같습니다.

 

분명 Twitter가 추가되었으나, 이를 Post에서 처리할 수 없기에 Twitter에 대해서는 아무 작업이 수행되지 않습니다.

 

 

즉 이를 해결하기 위해서는 SNS 타입의 새로운 구현체가 정의될 때마다 if문을 추가해 주어야 합니다.

 

 

 

 

 

SNS가 추가될 때마다 기존의 Post의 코드가 바뀌는 것은 OCP를 위반합니다.

이제부터 이를 해결하는 방법을 알아보도록 하겠습니다.

 

 

 

 

 

 

 

 

 

 

 

 

1. Post의 postOn 메서드의 파라미터를 구현체로 구체화하여 정의한다.

우선 이 방법은 해결책이 될 수 없음을 미리 이야기하고 살펴보겠습니다.

 

이 방법을 적용한 Post 코드는 다음과 같습니다.

	interface Post {
		void postOn(Facebook sns);
		void postOn(Instagram sns);
	}

	static class Text implements Post {
		@Override
		public void postOn(Facebook sns) {
			System.out.println("Text postOn [Facebook]");
		}

		@Override
		public void postOn(Instagram sns) {
			System.out.println("Text postOn [Instagram]");
		}
	}

	static class Picture implements Post {
		@Override
		public void postOn(Facebook sns) {
			System.out.println("Picture postOn [Facebook]");
		}

		@Override
		public void postOn(Instagram sns) {
			System.out.println("Picture postOn [Instagram]");
		}
	}

이는 새로운 SNS 구현체가 등장하였을 때, 기존 코드를 수정하는 것이 아닌 해당 구현체에 대한 postOn을 추가하여 주면 되기 때문에 OCP를 위반하지 않습니다.

 

 

그러나 이는 해결방법이 될 수 없습니다.

 

다음과 같이 다형성을 이용할 수 없어지기 때문입니다.

 

 

 

 

 

 

 

 

 

 

 

 

 

2. Double Dispatch 사용

 

import java.util.List;

public class Dispatch {
	interface Post {
		void postOn(SNS sns);
	}

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

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

	interface SNS {
		void post(Text post);
		void post(Picture post);
	}

	static class Facebook implements SNS {
		@Override
		public void post(Text post) {
			System.out.println("Text postOn [Facebook]");
		}

		@Override
		public void post(Picture post) {
			System.out.println("Picture postOn [Facebook]");
		}
	}

	static class Instagram implements SNS {
		@Override
		public void post(Text post) {
			System.out.println("Text postOn [Facebook]");
		}

		@Override
		public void post(Picture post) {
			System.out.println("Picture postOn [Facebook]");
		}
	}

	public static void main(String[] args) {
		List<Post> posts = List.of(new Text(), new Picture());
		List<SNS> snss = List.of(new Facebook(), new Instagram());

		posts.forEach(post -> snss.forEach(post::postOn));
	}
}

 

 

위 코드에서는 다음과 같은 Dynamic Dispatch가 2번 발생합니다.

 

1. post의 postOn 실행 시 Dynamic Dispatch가 1번 발생합니다.

2. postOn 메서드 내부에서 sns의 post 실행 시 Dynamic Dispatch가 1번 발생합니다.

 

이처럼 Dynamic Dispatch가 2번 진행되는 것을 Double Dispatch라 합니다.

 

 

 

두가지 이상의 하위타입을 갖는 타입들을 조합하여 이차원적인 비즈니스 로직 구조가 만들어지는 경우에는

위처럼 Double Dispatch를 사용하여 해결할 수 있습니다.

 

 

 

 

 

 

 

 

 

 

 

 

Double Dispatch와 Visitor Pattern

 

Double Dispatch는 Visitor Pattern보다 더 일반화된(추상적인) 형태라고 볼 수 있습니다.

 

기능은 똑같이 두고, 메서드와 클래스의 이름만 조금 변경하여 Visitor Pattern과 비슷하게 변경해보면 다음과 같습니다.

interface Post {
   void accept(PostVisitor visitor);
}

static class Text implements Post {
   @Override
   public void accept(PostVisitor visitor) {
      visitor.visit(this);
   }
}

static class Picture implements Post {
   @Override
   public void accept(PostVisitor visitor) {
      visitor.visit(this);
   }
}

interface PostVisitor {
   void visit(Text post);
   void visit(Picture post);
}

static class PostOnFacebookVisitor implements PostVisitor {
   @Override
   public void visit(Text post) {
      System.out.println("Text postOn [Facebook]");
   }

   @Override
   public void visit(Picture post) {
      System.out.println("Picture postOn [Facebook]");
   }
}

static class PostOnInstagramVisitor implements PostVisitor {
   @Override
   public void visit(Text post) {
      System.out.println("Text postOn [Facebook]");
   }

   @Override
   public void visit(Picture post) {
      System.out.println("Picture postOn [Facebook]");
   }
}

public static void main(String[] args) {
   List<Post> posts = List.of(new Text(), new Picture());
   List<PostVisitor> postVisitors = List.of(new PostOnFacebookVisitor(), new PostOnInstagramVisitor());

   posts.forEach(post -> postVisitors.forEach(post::accept));
}

 

 

 

 

 

 

 

 

Reference

https://www.youtube.com/watch?v=s-tXAHub6vg&list=PLv-xDnFD-nnmof-yoZQN8Fs2kVljIuFyC&index=16 

 

 

 

728x90