가장 정형적인 템플릿/콜백 패턴의 후보는 try/catch/finally 블록을 사용하는 코드이다. 일정한 리소스를 만들거나 가져와 작업하면서, 예외가 발생할 가능성이 있는 코드는 보통 이 구조로 만들어짐 하지만 중첩된 구조로 될 경우엔, 가독성의 문제를 처리하고, 사이드 이펙트를 야기한다.
샘플 1 (일반)
파일을 하나 열어서 모든 라인의 숫자를 더한 합을 돌려주는 코드를 만드는 예시
샘플코드
packagecom.company;importjava.io.BufferedReader;importjava.io.FileReader;importjava.io.IOException;publicclassCalculator {publicIntegercalcSum(String filePath) throwsIOException{BufferedReader br =newBufferedReader(new FileReader(filePath));Integer sum =0;String line =null;while((line =br.readLine())!=null){ sum +=Integer.valueOf(line); }br.close();return sum; }}
테스트 코드
packagecom.company;importorg.junit.jupiter.api.Test;importjava.io.IOException;importstaticorg.hamcrest.CoreMatchers.is;importstaticorg.hamcrest.MatcherAssert.assertThat;classCalculatorTest { @TestpublicvoidsumOfNumbers() throwsIOException{Calculator calculator =newCalculator();int sum =calculator.calcSum("src/main/resources/numbers.txt");assertThat(sum,is(10)); }}
위의 코드를 살펴보면, 파일을 읽거나 처리하다가 예외가 발생하면, 파일이 정상적으로 닫히지 않고 메소드를 빠져나간다. 따라서 try/finally 블록을 적용해서 어떤 경우에라도 파일이 열렸으면, 반드시 닫아주도록 만들어줘야된다.
콜백이 돌려준 결과는 최종적으로 모든 처리를 마친 후에 다시 클라이언트에게 되돌려준다. BufferedReader를 만들어서 넘겨주는 것과 그 외의 모든 번거로운 작업에 대한 작업의 흐름은 템플릿에서 진행하고, 준비된 BufferedReader를 이용해 작업을 수행하는 부분은 콜백을 호출해서 처리한다.
템플릿으로 분리한 부분을 제외한 나머지 코드를 BufferedReaderCallback 인터페이스로 만든 익명 내부 클래스에 담는다. 처리할 파일의 경로와 함께 준비된 익명 내부 클래스의 오브젝트를 템플릿에 전달한다.
Template + Callback Method
publicIntegercalcSum(String filePath) throws IOException{BufferedReaderCallback sumCallback =newBufferedReaderCallback() { @OverridepublicIntegerdoSomethingWithReader(BufferedReader br) throwsIOException {Integer sum =0;String line =null;while((line =br.readLine())!=null){ sum +=Integer.valueOf(line); }return sum; } };returnfileReadTemplate(filePath,sumCallback); }
콜백함수를 구성하는 구조는 생각보다 다른언어들과 비슷하다. ex) JS 인터페이스 부분을 익명함수 선언을 하면서, 로직이 들어간다.
템플릿/콜백의 재설계
앞에서 봤던 로직들은 상당히 겹치는 부분이 많이 존재한다. 먼저 결과를 저장할 변수를 초기화하고, BufferedReader를 이용해 파일의 마지막 라인까지 순차적으로 읽으면서 각 라인에서 읽은 내용을 결과를 저장할 변수의 값과 함께 계산하다가, 다 읽었으면 결과를 저장하고 있는 변수의 값을 리턴한다.
TXT 파일의 현재 줄을 처리하면 연산된 결과를 리턴하는 공통적인 구조를 가지고 있기 때문에, 아래와 같이 인터페이스를 작성한다.