본 시리즈의 개요
될수있는 대로 토비님의 방송을 듣고 그 후기를 남겨볼까 합니다. 토비님의 정규방송 주소는 아래와 같고 일반적인 방송공지는 페북이나 슬렉에서 하시는 편입니다.
토비의 봄 TV
1회차 주제 – 의존성과 더블디스패치
의존성이 무엇인가와 복잡한 의존관계에 대한 해법으로서 더블디스패치 및 이를 이용한 비지터 패턴, 더 나아가 이를 활용한 JPA에서의 프록시비지터 패턴에 대한 간략한 소개를 합니다.
의존성 – Dependency
의존성은 client와 supplier가 있는 경우 다음과 같이 설명할 수 있습니다.
- Supplier의 변화가 Client에게 영향을 준다.
- 의존성으로 말미암아 Client의 재사용성이 어렵다.
이를 보다 구체적으로 살펴보면 클래스 차원의 의존성이라 생각해볼 수 있습니다.
- 클래스 의존성(class dependency) : Client —-> Supplier Class
하지만 인터페이스에 대한 의존성으로 옮기면 정적인 의존성에서 런타임 의존성으로 변경할 수 있습니다.
- 인터페이스 의존성(Programming to Interface) : Client —-> Supplier Interface
또한 인터페이스 의존성으로 변이하면 객체 합성으로 전환할 수 있는 토대가 되고 이 경우 인터페이스에 의존하고 있는 Client가 구조화되고 안정화되어 다양한 구상클래스를 받아들일 수 있다면 이를 Framework라 칭할 수 있습니다(라고..^^)
디자인패턴책에서는 디자인 패턴을 두 가지 축으로 나누는데 이 중 오브젝트 패턴은 다음과 같이 설명됩니다.
오브젝트 패턴은 런타임시에 바뀔 수 있는 상속관계보다 더 동적인 오브젝트 의존관계를 다룬다. -GoF
오브젝트 패턴이 이러한 런타임의존성에 대해 다루므로 DI는 오브젝트 패턴 전체에 적용할 수 있습니다.
정적 디스패치와 동적 디스패치
정적디스패치
컴파일 시점에 무엇이 실행될 지 알고 있고 bytecode에도 남아있습니다.메소드 오버로딩을 포함하여 오버라이드 전부 컴파일시점에 결정됩니다.
public class Dispatch{ static class Service{ void run(){ System.out.println("run()"); } 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(); new Service().run(1); new Service().run("Dispatch"); } }
동적디스패치
아래 코드에서 svc.run() 호출 시 눈에 보이지 않지만 receiver parameter로 this가 들어가 있습니다.
this는 MyService1 또는 MyService2의 인스턴스이므로 이를 바탕으로 올바른 run을 호출할 수 있습니다.
public class Dispatch{ static abstract class Service{ abstract void run(); } static class MyService1 extends Service{ @Override void run(){ System.out.println("run1"); } } static class MyService1 extends Service{ @Override void run(){ System.out.println("run2"); } } public static void main(String[] args){ MyService1 svc = new MyService1(); svc.run(); //run1 - 정적 디스패치 MyService2 svc2 = new MyService2(); svc2.run(); //run2 - 정적 디스패치 //동적디스패치 Service svc3 = new MyService1(); svc.run(); //run1 - 리시버파라메터(this)를 활용하면 메소드를 찾아냄 //-------------------------------------- List<Service> svc = Array.asList(new MyService1(), new MyService2()); svc.forEach((Service s) -> s.run()); svc.forEach(s -> s.run()); //인자의 타입추론메소드타입추론 svc.forEach(Service::run); //Service::run으로 추론처리 } }
토막상식
* Java Method Signature – name, parameter types 오버라이딩가능. 2개 정의 불가합니다.
* Method Type – return type, method type parameter, method argument types, exception 함수의 레퍼런스로 사용가능합니다. 위의 예에서 Service::run에 사용되고 있습니다.
더블디스패치
동적 디스패치를 두 번하는 기법입니다.
기본적인 예제. 4가지 조합이 나오는 추상 Post레벨과 이를 활용하는 SNS레벨간의 조합처리되는 예제입니다.
public class Dispatch{ 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()); /* for(Post p:posts){ for(SNS s:sns){ p.postOn(s); } } */ posts.forEach(p->sns.forEach(s.postOn(s))); }
여기서 개별 SNS를 instanceof를 통해 처리하면 다음과 같은 문제가 발생합니다.
1. OCP위반
2. 경우의 수가 변하는 경우 대응이 불가(GooglePlus)
3. 모든 경우의 수를 처리하기 힘듬
public class Dispatch{ 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{} static class GooglePlus 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()); /* for(Post p:posts){ for(SNS s:sns){ p.postOn(s); } } */ posts.forEach(p->sns.forEach(s.postOn(s))); }
위의 예에서 새롭게 GooglePlus가 도입되었지만 Text와 Picture에 대응을 빼먹게 됩니다.
이를 막고자 Post레벨에서 하위클래스를 명시하는 메소드를 개별로 정의해도 정적디스패치에서 에러발생하게 됩니다.
public class Dispatch{ 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{} static class GooglePlus 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()); /* for(Post p:posts){ for(SNS s:sns){ p.postOn(s); } } */ posts.forEach(p->sns.forEach(s.postOn(s))); //컴파일에러발생 (SNS타입이어야함) }
이러한 문제의 해법 중 하나로 더블디스패치가 있습니다. 최초 등장한 논문은 1986년의 a simple technique for handing multuple polymophism에서 입니다.
파라메터에 타입을 구분지으려던 전략을 포기하고 메소드 내에서 다시 동적디스패치로 처리하는 테크닉입니다. 같은 원리를 적용하면 링크드 리스트처럼 몇 번이라도 디스패치할 수 있지만. 현실적으로 트리플디스패치도 일어나기는 힘듭니다(켄트백 구현패턴중) 토비님도 거의 잘 안쓰지만 자바 내부의 애노태이션 처리기라던가 특수한 경우에 사용 중이라고 보여주셨습니다.
- multi dispatching method – 멀티디스패칭이 가능한 구조를 갖고 있는 언어. commonlisp, julia. 자바는 싱글리시버(this)만 갖고 있음(개인적으로 swift도 가능하다고 생각합니다)
더블디스패치를 활용한 수정예제
public class Dispatch{ 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"); } } //구글 플러스 추가! static class GooglePlus implements SNS{ public void post(Text post){ System.out.println("text -> gplus"); } public void post(Picture post){ System.out.println("picture -> gplus"); } } public static void main(String[] args){ List<Post> posts = Arrays.asList(new Text(), new Picture()); List<SNS> sns = Arrays.asList(new Facebook(), new Twitter(), new GooglePlus()); /* for(Post p:posts){ for(SNS s:sns){ p.postOn(s); } } */ posts.forEach(p->sns.forEach(s.postOn(s))); }
하지만 더블디스패치는 인터페이스쪽의 변화가 구현쪽 전체에 영향을 끼치므로 이런 변화가 예상되는 경우는 구현쪽의 추상층을 인터페이스보다는 기본 구현이 포함되는 추상클래스가 유리합니다.
visitor pattern은 이러한 더블디스패치를 이용한 패턴입니다.
JPA를 통해 폴리모픽 쿼리를 하는 경우 쿼리결과가 프록시로 오기 때문에 instanceof를 쓰는 것 자체가 힘들어지므로 비지터패턴을 자주 쓰게 되고 이를 일반화하여 JPA커뮤니티에서는 Proxy visitor pattern이라 부르고 있습니다(라고 하셨습니다만 구체적인 예는 시간관계상 생략)
정리
9시반부터 시험방송시작, 10시부터 정규방송이 시작되어 11시반 종료되었습니다. 굉장히 긴시간동안 목소리톤도 일정하고 진행속도 일정하고 무엇보다 타이핑속도가 굉장히 빨랐습니다(후훗 저도 빠릅니다)
차회 예고 – 리액티브 프로그래밍, 비동기 프로그래밍 흐름에 대해서 다루실 예정입입니다만 좀 어정쩡하게 예고하셔서 그대로 될지는 모르겠습니다.
P.S
방송이 끝나고 슬랙에서 질의응답했던 내용을 참고삼아 올려둡니다.
recent comment