템플릿/콜백 예제를 하나 만들어보자. 

파일을 하나 열어서 모든 라인의 숫자를 더한 합을 돌려주는 코드를 만들어 보겠다. 다음과 같이 네 개의 숫자를 담고 있는 numbers.txt파일을 하나 준비한다.

1
2
3
4

이제 해당 파일의 경로를 주면 모든 숫자의 합을 돌려 주는 함수와 곱을 돌려주는 함수를 만들어 보자. 

package springbook.learningtest.template;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
//중복제거 
//-> 반복되지 않는 부분 추출 

public class Calculator {
	BufferedReader br = null;
	String num="";
	Integer result=0;

	public Integer calcSum(String filePath)throws IOException{		
		try {
			br=new BufferedReader(new FileReader(filePath));
			while((num = br.readLine()) != null) {
				result += Integer.valueOf(num);
			}
			return result;
		} catch (IOException e) {
			// TODO Auto-generated catch block
			System.out.println(e.getMessage());
			throw e;
		}finally {
			if(br != null) {
				try {
					br.close();
				} catch (IOException e1) {
					// TODO Auto-generated catch block
					System.out.println(e1.getMessage()) ;
				}
			}
		}		
	}
	public Integer calcMulti(String filePath) throws IOException{		
		try {
			br=new BufferedReader(new FileReader(filePath));
			while((num = br.readLine()) != null) {
				result *= Integer.valueOf(num);
			}
			return result;
		} catch (IOException e) {
			// TODO Auto-generated catch block
			System.out.println(e.getMessage());
			throw e;
		}finally {
			if(br != null) {
				try {
					br.close();
				} catch (IOException e1) {
					// TODO Auto-generated catch block
					System.out.println(e1.getMessage()) ;
				}
			}
		}		
	}
}

이제 아래의 순서로 진행해보자


  1. 중복의 제거와 템플릿/콜백설계
  2. 템플릿/콜백의 재설계
  3. 제네릭스를 이용한 콜백 인터페이스 

1. 중복의 제거와 템플릿/콜백설계

템플릿/콜백을 적용할 때는 템플릿과 콜백의 경계를 정하고 템플릿이 콜백에게, 콜백이 템플릿에게 각각 전달하는 내용이 무엇인지 파악하는게 중요하다. 지금부터 하나씩 찾아가며 진행해보자.

calcSum()함수와 calcMulti함수에서 바뀌는 부분만 따로 빼보자.

아래 코드는 바뀌는 부분을 따로 메소드 doSomethingWithReader()로 뺐다. 

public Integer doSomethingWithReader(BufferedReader br) throws NumberFormatException, IOException {
	Integer result=0;
	String num="";
	while((num = br.readLine()) != null) {
		result += Integer.valueOf(num);
	}		
	return result;
}
public Integer calcSum(String filePath)throws IOException{		
	try {
		br=new BufferedReader(new FileReader(filePath));
		result=doSomethingWithReader(br);
		return result;
	} catch (IOException e) {
		// TODO Auto-generated catch block
		System.out.println(e.getMessage());
		throw e;
	}finally {
		if(br != null) {
			try {
				br.close();
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				System.out.println(e1.getMessage()) ;
			}
		}
	}		
}

하지만 해당 method는 calcSum()메소드에서만 사용이 가능하므로 다른 메소드에서 재사용이 불가능했다. 그래서 doSomthingwithReader() 함수를 Interface 클래스의 메소드로 만들어서 재사용이 가능하도록 만들었다. 

public interface BufferedReaderCallback {
	public Integer doSomethingWithReader(BufferedReader br)throws IOException;
}

 이번엔 변하지 않는 부분을 Template(Context와 같은 개념으로 보면된다)함수로 빼보자 

public Integer fileReaderTemplate(String filePath,BufferedReaderCallback callback) throws IOException {
	try {
		br=new BufferedReader(new FileReader(filePath));
		Integer result=callback.doSomethingWithReader(br);
		return result;
	} catch (IOException e) {
		// TODO Auto-generated catch block
		System.out.println(e.getMessage());
		throw e;
	}finally {
		if(br != null) {
			try {
				br.close();
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				System.out.println(e1.getMessage()) ;
			}
		}
	}		
}
	

Template함수를 만들었다면 이제 calcSum() 메소드와 calcMulti()메소드도  Template에 전략 객체를 만들어 넘겨주도록 수정이 필요하다. 전략객체를 만드는 부분은 익명내부클래스를 사용해서 구현하였다. 

package springbook.learningtest.template;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class Calculator {
	BufferedReader br = null;	
	
	public Integer fileReaderTemplate(String filePath,BufferedReaderCallback callback) throws IOException {
		try {
			br=new BufferedReader(new FileReader(filePath));
			Integer result=callback.doSomethingWithReader(br);
			return result;
		} catch (IOException e) {
			// TODO Auto-generated catch block
			System.out.println(e.getMessage());
			throw e;
		}finally {
			if(br != null) {
				try {
					br.close();
				} catch (IOException e1) {
					// TODO Auto-generated catch block
					System.out.println(e1.getMessage()) ;
				}
			}
		}		
	}
	public Integer calcSum(final String filePath)throws IOException{
		return fileReaderTemplate(filePath, 
					new BufferedReaderCallback(){
						@Override
						public Integer doSomethingWithReader(BufferedReader br) throws IOException{
							String num=null;
							Integer result=0;
							while((num = br.readLine()) != null) {
								result += Integer.valueOf(num);
							}		
							return result;
						}
					}
				);	
	}
	public Integer calcMulti(String filePath) throws IOException{		
		return fileReaderTemplate(filePath, 
					new BufferedReaderCallback(){
						@Override
						public Integer doSomethingWithReader(BufferedReader br) throws IOException{
							String num=null;
							Integer result=0;
							while((num = br.readLine()) != null) {
								result *= Integer.valueOf(num);
							}		
							return result;
						}
					}
				);	
	}
}

2. 템플릿/콜백의 재설계

중복 제거를 하면서 코드가 간략해졌다. 하지만 아직 중복 되는 부분이 존재한다.  중복되는 부분을 다시 한번 찾아서 템플릿/콜백 재설계를 해보자.

지금 부터 calcMulit()함수와 calcSum() 함수에서 callback을 생성 해주는 부분을 비교해보자.

//calcSum()
public Integer doSomethingWithReader(BufferedReader br) throws IOException{
	String num=null;
	Integer result=0;
	while((num = br.readLine()) != null) {
		result += Integer.valueOf(num);
	}		
	return result;
}
//calcMulti()
public Integer doSomethingWithReader(BufferedReader br) throws IOException{
	String num=null;
	Integer result=1;
	while((num = br.readLine()) != null) {
		result *= Integer.valueOf(num);
	}		
	return result;
}

반복되는 부분이 보이는가? 조금만 살펴보면 두코드가 상당 부분이 반복되고 있다는 것을 알수 있다. 아래에 반복되는 부분을 표시해 두었다. 

템플릿과 콜백을 찾을때는 변하는 코드의 경계를 찾고 그경계를 사이에 두고 주고받는 일정한 정보가 있는지 확인 하면된다고 했다.

위코드에서 바뀌는 부분(변하는 코드의 경계)은 사실 다섯번째 줄이다. (이외에 세번째 줄도 바뀌기는 하나, 이값은 Template에 파라미터로 넘겨줄것이다.)

result +=Integer.valueOf(num);

result *=Integer.valueOf(num);

다섯번째줄에 전달 되는 정보는 선언한 변수 값인 num 값이다. 

변하는 코드의 경계와 전달 되는 정보를 찾았으니, 콜백 인터페이스(전략에 해당된다.)를 다시 만들어보자. 

public interface LineCallback {
	public Integer doSomethingWithLine(Integer result,String num) ;
}

 Template(Context)  와 calcSum(), calcMulti() 메소드를 LineCallback 객체(전략)을 생성하여 Template(Context)에 넘겨주도록 수정해보자.  아래 코드를 보면 filereaderTemplate() 메소드에 파라미터로 initVal를 넘겨주고 있다. 이 파라미터는 result의 초기값을 설정해준다.  

public class Calculator {
	BufferedReader br = null;	
	
	public Integer lineReaderTemplate(String filePath,LineCallback callback,int initVal) throws IOException {
		try {
			br=new BufferedReader(new FileReader(filePath));
			String num=null;
			Integer result=initVal;
			while((num = br.readLine()) != null) {
				result=callback.doSomethingWithLine(result, num);
			}		
			return result;
		} catch (IOException e) {
			// TODO Auto-generated catch block
			System.out.println(e.getMessage());
			throw e;
		}finally {
			if(br != null) {
				try {
					br.close();
				} catch (IOException e1) {
					// TODO Auto-generated catch block
					System.out.println(e1.getMessage()) ;
				}
			}
		}		
	}
	public Integer calcSum(final String filePath)throws IOException{
		return lineReaderTemplate(filePath, 
    				new LineCallback() {
                       public Integer doSomethingWithLine(Integer result,Integer num) {
                          return result++Integer.parseInt(num);
                       }},0);	
	}
	public Integer calcMulti(String filePath) throws IOException{		
		return lineReaderTemplate(filePath, 
        			new LineCallback() {
                       public Integer doSomethingWithLine(Integer result,Integer num) {
                         return result*+Integer.parseInt(num);
                       }},1);		
	}
}

처음 템플릿/ 콜백 설계 했을때는 파일의 각 라인을 루프로 돌면서 가져오는 부분도 전략에 포함 시켰으나, 템플릿/콜백 재설계를 하면서 이부분도 Template(Context)로 빼버렸다. 각 라인의 내용을 가지고 계산하는 작업만 전략으로 빼도록 재설계 했다.   

3. 제네릭스를 이용한 콜백 인터페이스 

지금까지 사용한 LineCallback와 lineReadTemplate()은 Integer로 고정되어 있다. 만약 파일의 라인단위를 처리해서 다양한 결과의 타입을 가져가고 싶다면 어떻게 하면 될까? 자바에서는 제네릭스를 이용하면 된다.

한 예로 파일의 각 라인을 읽어서 하나의 스트링으로 돌려주는 기능이 추가된다고 해보자. 이번에는 템플릿이 리턴하는 타입이 스트링이어야 한다. 콜백의 작업 결과도 스트링이어야 한다. 그럼 지금부터 제네릭스를 이용할수 있도록 수정해보자. 

제네릭스를 이용하여 전략 Interface를 변경했다.

public interface LineCallback<T> {
	public T doSomethingWithLine(T result,T num) ;

}

LineCallback와 lineReadTemplate()도 변경해보자.

public class Calculator {
	BufferedReader br = null;	
	
	public <T> T lineReaderTemplate(String filePath,LineCallback<T> callback,T initVal) throws IOException {
		try {
			br=new BufferedReader(new FileReader(filePath));
			String num=null;
			T result=initVal;
			while((num = br.readLine()) != null) {
				result=callback.doSomethingWithLine(result, num);
			}		
			return result;
		} catch (IOException e) {
			// TODO Auto-generated catch block
			System.out.println(e.getMessage());
			throw e;
		}finally {
			if(br != null) {
				try {
					br.close();
				} catch (IOException e1) {
					// TODO Auto-generated catch block
					System.out.println(e1.getMessage()) ;
				}
			}
		}		
	}
	public Integer calcSum(final String filePath)throws IOException{					
		return lineReaderTemplate(filePath, new LineCallback<Integer>() {
									@Override
									public Integer doSomethingWithLine(Integer result, String num) {
										// TODO Auto-generated method stub
										return result + Integer.parseInt(num);
									}},0);	
	}
	public Integer calcMulti(String filePath) throws IOException{		
		return lineReaderTemplate(filePath, new LineCallback<Integer>() {
										@Override
										public Integer doSomethingWithLine(Integer result, String num) {
											// TODO Auto-generated method stub
											return result * Integer.parseInt(num);
										}},1);		
	}
}

  이제 마지막으로 파일의 각 라인을 읽어서 하나의 스트링으로 돌려주는 기능을 새로 추가해보자. 

public String concentrate(String filePath) throws IOException{
		return lineReaderTemplate(filePath, new LineCallback<String>() {
			@Override
			public String doSomethingWithLine(String result, String value) {
				// TODO Auto-generated method stub
				return result + value;
			}},"");		
}

3장 템플릿 내용을 다 적용해 볼 수 있는 코드를 만들어 보았다. 이 코드를  직접 만들어 본다면 3장 템플릿의 주요 핵심 개념인 템플릿/콜백을 정확히 이해 할 수 있을 것이다. 

'Spring' 카테고리의 다른 글

5장 서비스 추상화-I  (0) 2020.01.14
4장 예외  (0) 2020.01.05
3장 템플릿 III - 스프링의 JdbcTemplate  (0) 2020.01.04
3장 템플릿 II- 템플릿/ 콜백패턴  (0) 2020.01.04
3장 템플릿I - 전략패턴  (0) 2020.01.04

+ Recent posts