본문 바로가기
Spring&SpringBoot/<토비의 스프링 3.1 Vol1.1>, 이일민

콜백 분리 연습

by 민휘 2023. 3. 13.

3장 5절에서 Calculator 예제를 이용해 콜백(반복되지 않는 부분)을 분리하는 연습을 했다.

텍스트 파일의 숫자를 한줄씩 읽어 누적합과 누적곱 계산, 문자열처럼 연결하는 기능을 구현했다.

 

 

세 단계를 거쳐서 콜백을 분리했다.

  1. 콜백 분리 : 리소스 생성, 예외 처리, 리소스 닫기로 구성된 변하지 않는 부분으로부터, 파일 내용으로 무언가를 하는 변경되는 부분을 분리했다.
  2. Row 콜백 분리 : 라인을 하나씩 읽어 기존값에 연산을 적용하는 변하지 않는 부분으로부터, 합 혹은 곱이 적용되는 연산 종류를 분리했다. 
  3. 콜백에 제네릭 적용 : 라인을 하나씩 읽어 기존값에 연산을 적용하는 변하지 않는 부분으로부터, 타입과 연산 종류를 분리했다. (제네릭 타입 콜백)

 

 

중복이 발생한 코드의 모습

filePath를 인자로 받아 파일을 열고 라인을 하나씩 읽어 누적합, 누적곱을 계산하는 메소드이다.

public class Calculator {
	public Integer calcSum(String filepath) throws IOException { 
        BufferedReader br = null;
        try {
            br = new BufferedReader(new FileReader(filepath));
            Integer sum = 0;
            String line = null;
            while((line = br.readLine()) != null)
                sum += Integer.valueOf(line);
            return sum;
        } catch (IOException e) {
            System.out.println(e.getMessage());
            throw e;
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    System.out.println(e.getMessage());
                }
            }

        }
    }
    
    public Integer calcMultiply(String filepath) throws IOException {
        BufferedReader br = null;
        try {
            br = new BufferedReader(new FileReader(filepath));
            Integer multiply = 1;
            String line = null;
            while((line = br.readLine()) != null)
                multiply *= Integer.valueOf(line);
            return multiply;
        } catch (IOException e) {
            System.out.println(e.getMessage());
            throw e;
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    System.out.println(e.getMessage());
                }
            }

        }
    }
      
}

 

 

1차 개선

파일을 열어 BufferedReader를 받고, 예외 처리하고, 리소스를 반환하는 부분이 반복된다.

이 부분은 템플릿 메소드로 추출한다.

public class Calculator {
	private Integer fileReadTemplate(String filepath, BufferedReaderCallback callback) throws IOException { // method di
        BufferedReader br = null;

        try {
            br = new BufferedReader(new FileReader(filepath));
            return callback.doSomethingWithReader(br);
        } catch (IOException e) {
            System.out.println(e.getMessage());
            throw e;
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    System.out.println(e.getMessage());
                }
            }}}
}

 

바뀌는 부분은 BufferedReader를 받아 읽은 내용으로 무언가를 하는 부분이다.

합과 곱을 계산하는 콜백을 람다로 만들어서 사용하자.

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

public class Calculator {
    public Integer calcSum(String filePath) throws IOException {
        BufferedReaderCallback sumCallback = br -> {
            Integer sum = 0;
            String line = null;
            while((line = br.readLine()) != null)
                sum += Integer.valueOf(line);
            return sum;
        };
        return fileReadTemplate(filePath, sumCallback);
    }

    public int calcMultiply(String filePath) throws IOException {
        BufferedReaderCallback multiplyCallback = br -> {
            Integer multiply = 0;
            String line = null;
            while((line = br.readLine()) != null)
                multiply *= Integer.valueOf(line);
            return multiply;
        };
        return fileReadTemplate(filePath, multiplyCallback);
    }
}

 

 

2차 개선

BufferedReaderCallback 내부에서도 동일한 코드가 반복되고 있다.

초기값을 설정하고 한줄씩 읽어오는 부분이다.

초기화할 값과 연산 종류는 달라지므로 콜백으로 분리한다.

public interface LineCallback {
    Integer doSomethingWithLine(String line, Integer value);
}

public class Calculator {
	private Integer lineReadTemplate(String filepath, LineCallback callback, Integer initVal) throws IOException {
        BufferedReader br = null;

        try {
            br = new BufferedReader(new FileReader(filepath));
            Integer res = initVal;
            String line = null;
            while((line = br.readLine()) != null)
                callback.doSomethingWithLine(line, res);
            return res;
        } catch (IOException e) {
            System.out.println(e.getMessage());
            throw e;
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    System.out.println(e.getMessage());
                }
            }
        }
	    }

    public Integer calcSum(String filePath) throws IOException {
        LineCallback multiplyCallback = (line, value) -> value + Integer.valueOf(line);
        return lineReadTemplate(filePath, multiplyCallback, 0);
    }

    public Integer calcMultiply(String filePath) throws IOException {
        LineCallback sumCallback = (line, value) -> value * Integer.valueOf(line);
        return lineReadTemplate(filePath, sumCallback, 0);
    }
}

 

 

3차 개선

이번엔 Integer가 아니라 String을 반환하는 계산 메소드를 만들려고 한다.

라인을 읽고 누적 연결해서 문자열을 반환한다.

LineCallback에 제네릭 타입을 적용해보자.

템플릿이 콜백과 같은 타입의 값을 반환하므로 동일하게 제네릭 타입을 반환하도록 한다.

콜백을 생성해는 클라이언트에서 제네릭 타입을 결정할 때 템플릿과 콜백의 타입이 결정된다.

public interface LineCallback<T> {
    T doSomethingWithLine(String line, T value);
}

public class Calculator {
	private <T> T lineReadTemplate(String filepath, LineCallback<T> callback, T initVal) throws IOException {
        BufferedReader br = null;

        try {
            br = new BufferedReader(new FileReader(filepath));
            T res = initVal;
            String line = null;
            while((line = br.readLine()) != null)
                callback.doSomethingWithLine(line, res);
            return res;
        } catch (IOException e) {
            System.out.println(e.getMessage());
            throw e;
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    System.out.println(e.getMessage());
                }
            }
        }
    }

		public Integer calcSum(String filePath) throws IOException {
        LineCallback<Integer> multiplyCallback = (line, value) -> value + Integer.valueOf(line);
        return lineReadTemplate(filePath, multiplyCallback, 0);
    }

    public int calcMultiply(String filePath) throws IOException {
        LineCallback<Integer> sumCallback = (line, value) -> value * Integer.valueOf(line);
        return lineReadTemplate(filePath, sumCallback, 0);
    }

    // 제네릭스를 이용한 문자열 연산
    public String concatenateStrings(String filePath) throws IOException {
        LineCallback<String> concatenateCallback = (line, value) -> value + line;
        return lineReadTemplate(filePath, concatenateCallback, "");
    }
}