티스토리 뷰
앞선 두 포스팅을 통해 전략패턴(Strategy Pattern)과 템플릿 메서드 패턴(Template Method Pattern)에 대해 알아봤다. 이번에 알아볼 패턴은 템플릿 콜백 패턴(Template Callback Pattern)인데 앞선 두 패턴을 적절히 혼합한 형태라 크게 어려운 부분은 없다.
전략 패턴의 경우 재사용하고자하는 클래스를 상속이 아닌 인스턴스 변수로 취급하는 합성(Composite)을 이용한다. 그리고 인터페이스를 활용한 다형성과 setter 메서드를 이용해 사용하고자하는 전략(인스턴스 변수가 참조하는 객체)을 필요할때마다 변경해가며 호출함으로서 변화에는 닫히고 확장에는 열린 소스를 짤수있게하는 패턴이다.
템플릿 메서드 패턴의 경우는 템플릿이라는 단어에서부터 알수있듯이 크게 돌아가는 메서드 내에서 부분적으로 오버라이딩을 활용해 메서드의 동작을 제어한다.
전략 패턴이 클래스 레벨의 확장을 추구한다면 템플릿 메서드 패턴은 메서드 레벨의 확장을 추구하는 것이다. 그렇다면 이 둘을 적절히 섞는다면 어떻게 하면 될까? 크게 돌아가는 메서드 내에서 부분적으로 확장이 필요한 메서드를 오버라이딩이 아니라 메서드(전략)를 주입받아 해당 메서드를 실행하면 된다. 말로만 설명하면 잘 이해가 안갈수있는데 사실 자바스크립트같은 언어에서는 아무렇지않게, 매우 당연하다는 듯이 받아들이고 사용하고있는개념이다. 잠시 jQuery 사용 소스를 보자.
jQuery(document).ready(function(){
});
다년의 경험이 쌓인 개발자가 아니라도 jQuery를 사용하는 프로젝트를 한번이라도 진행해봤다면 저 코드는 눈감고도 칠수있을정도로 친숙한 코드일것이다. 다만 저 코드를 분석해가며 쓰는경우는 흔치않을텐데 공백제외하고 1줄이라고도 할수있는 저 소스를 분석해보자면
jQuery() 함수다. ()가 붙은걸보니 함수를 호출하는 부분이다. ()안에 document가 들어가있다. jQuery() 함수를 호출하는데 인자로 document 객체를 보낸다. 그 뒤에 .ready() 가 있다. jQuery(document) 가 뭘반환하는지는 잘 모르겠지만 저 소스가 에러없이 실행된다는건 ready() 속성을 갖고있는 객체가 반환되는것같다. ready() 도 ()가 붙는걸보니 함수다. 더욱이 ()가 붙는다는건 함수를 호출한다는 뜻이다. ()안에 인자를 보니 function이다. 인자로 익명함수를 전달하고있는것이다. 그럼 이 인자는 누가 실행하나?
말로한 설명이 어렵다면 이렇게 풀어서 볼수있다.
function callback(){
}
var obj = jQuery(document);
obj.ready(callback);
jQuery() 함수가 반환한 객체를 이용해 ready() 함수를 실행시키고, 해당 함수에 callback 함수를 전달한다. 그런데 가만히 보니 callback()이 아니라 callback이다. 함수를 내가 실행하는게 아니라 함수 그 자체를 인자로 보낸다는것이다. 함수 그 자체를 인자로 전달받은 ready() 함수가 자기가 해야할일들을 한 다음 인자로 받은 callback함수를 실행시키는 것이다.
자바에서는 콜백이라는 개념이 좀 생소할수도있어서 자바스크립트 소스를 갖고 설명했는데 결국 콜백이란 어렵게 생각할 필요없이 함수 그 자체를 전달하는것이다.
결국 외부에서 함수를 주입한다는 뜻인데 이는 곧 위에서 말한 템플릿 콜백 패턴의 설명과 일치한다. 그런데 자바에서는 메서드를 전달할 수가 없는데?
자바는 언어 자체가 메서드의 전달을 허용하지않는다. 좀 어렵게 말하면 함수형 프로그래밍이니 일급객체니 하는 단어들이 나오는데 그런건 다른데서 배우고 메서드를 전달하는것 자체만 알아보자.
언어 자체가 메서드만 왔다갔다 하는걸 지원하지않기때문에 자바에서 콜백을 구현하기 위해서는 어쩔수없이 객체를 생성해야한다. 그런데 고작 콜백으로 호출할 메서드하나 보내자고 클래스파일을 만들면 너무 난잡해진다. 그리고 그렇게할거면 그냥 템플릿 메서드 패턴쓰지 굳이 공부하기힘들게 디자인패턴 하나 더 만들이유도없다. 그래서 이부분을 '익명 내부 클래스'로 해결한다. 예제로 보자.
interface Calculator{
public int calculation(int num1, int num2);
}
class A{
public int templateMethod(int num1, int num2, Calculator c){
System.out.println("입력받은 값 => [" + num1 + ", " + num2 + "]");
int result = c.calculation(num1, num2);
return result;
}
}
public class Test{
public static void main(String[] arg){
A a = new A();
System.out.println(a.templateMethod(5, 10, new Calculator(){
@Override
public int calculation(int num1, int num2){
return num1 + num2;
}
})); //더하기 수행
System.out.println(a.templateMethod(5, 10, new Calculator(){
@Override
public int calculation(int num1, int num2){
return num1 * num2;
}
})); //곱하기 수행
}
}
지난번 템플릿 메서드 패턴의 예제를 템플릿 콜백 패턴 형태로 바꿨다. 원래 사용될 템플릿 메서드를 갖고있는 클래스를 추상클래스로 선언한 후 해당 클래스를 상속받는 서브클래스에서 확장이 필요한 부분을 오버라이딩해서 사용하는 형태였는데, 확장해야하는 메서드를 전략 패턴처럼 외부에서 주입받아 호출하는 형태로 바꿨다. 그리고 누누히 말하듯 메서드를 인자로 쓸수없기때문에 익명내부클래스를 이용해 객체를 전달하고있다. 저부분을 완전한 전략패턴으로 구현한다거나 템플릿 메서드패턴으로 구현했다면 추가적인 요구사항(나누기, 빼기 등등)이 들어올때마다 메서드하나 때문에 구현클래스를 늘려나가야하는 상황이 왔을수도 있다. 또한 기존 템플릿 메서드 패턴의 단점이었던 상속을 이용했기때문에 슈퍼클래스와 서브클래스간의 결합도가 너무나도 단단해지는 점 마저 타파가 가능해졌다.
익명클래스를 잘 사용하지않아본 사람들은 저 소스가 좀 생소할수도있겠지만 패턴자체가 이해하기힘들다거나 하는부분은 없을것이다(전략패턴을 이미 잘 이해하고있다는 전제가 있어야한다.). 앞으로도 디자인패턴 포스팅을 몇개 더 하겠지만 이름은 분명히 쌩판 다른데 구현소스를 보니 별 차이가없어보인다고 생각될수도있는 패턴들이 생각보다 많다. 디자인패턴은 소스 그자체로 패턴들을 구분하기보다는 애초에 어떤 목적으로 이런 패턴이 나왔는지를 고민해야 구분하기가 쉽다. 서로 다른 문제를 해결하기위해 패턴이 나왔는데 구현해놓고보니 원래있던것과 크게 다르지않을수있다는것이다. 그래서 소스보다는 목적을 잘 파악하고 이해하는게 중요하다. 그리고 그 이해를 바탕으로 현재 직면한 문제에 적절한 패턴을 고르는것이 사실 디자인패턴 그 자체를 공부하는것보다 어렵다.
다시 돌아와서 위 소스를 잠깐만 더 보자면, 메서드하나 보내겠다고 .java파일이 늘어가는것 자체는 막았지만 생각하기에 따라 메서드하나 보내겠다고 저런식으로 작성하는것도 난잡하게 보일수도있다. 이 문제는 java 1.8에서부터 지원하는 람다를 이용해 해결할수있다. 람다예제를 끝으로 오늘 포스팅을 마치겠다.
@FunctionalInterface //함수형인터페이스라는걸 보장해주는 애노테이션.
interface Calculator{
//추상메서드가 1개인 인터페이스를 함수형인터페이스라한다.
public int calculation(int num1, int num2);
}
class A{
public int templateMethod(int num1, int num2, Calculator c){
System.out.println("입력받은 값 => [" + num1 + ", " + num2 + "]");
int result = c.calculation(num1, num2);
return result;
}
}
public class Test{
public static void main(String[] arg){
A a = new A();
//람다표현식을 이용한 더하기
System.out.println(a.templateMethod(5, 10, (num1, num2) -> num1 + num2));
//람다표현식을 이용한 곱하기
System.out.println(a.templateMethod(5, 10, (num1, num2) -> num1 * num2));
}
}
'Java' 카테고리의 다른 글
Generics (7) | 2016.05.29 |
---|---|
DesignPattern#04 Decorator Pattern (0) | 2016.03.10 |
DesignPattern#02 Template Method Pattern (0) | 2016.03.02 |
DesignPattern#01 Strategy Pattern (0) | 2016.03.01 |
MVC 구조에서 service와 serviceImpl (41) | 2016.02.27 |
- Total
- Today
- Yesterday
- OOP
- DesignPattern
- toby
- http
- servlet
- spring cloud
- TEST
- 정규표현식
- Design Pattern
- frontend개발환경
- programming
- code
- java
- JavaScript Core
- Jackson
- mariadb
- backend개발환경
- javascript
- db
- go-core
- generics
- JPA
- java8
- Git
- Kotlin
- MySQL
- EffectiveJava
- Spring
- frontcode
- clean code
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |