본문 바로가기
spring/토비의 스프링

토비의 스프링 Vol. 1 정리 - 3장 템플릿 - 2

by Empering 2020. 4. 19.
반응형

토비의 스프링 Vol.1 정리 - 3 - 2

3장 템플릿


템플릿/콜백의 응용

스프링의 많은  API나 기능을 살펴보면 템플릿/콜백 패턴을 적용한 경우를 많이 발견할 수 있다. 템플릿/콜백 패턴도 DI와 객체지향 설계를 적극적으로 응용한 결과다. 스프링에는 다양한 자바 엔터프라이즈 기술에서 사용할수 있도록 미리 만들어져 제공되는 수십 가지 템플릿/콜백 클래스와 API가 있다.

 

템플릿/콜백 예제

A 파일을 열어서 모든 라인의 숫자를 더한 합을 돌려주는 코드를 작성

 

A-0. 라인별로 1,2,3,4 를 작성한 txt 파일을 resources 폴더에 생성

 

A-1. 테스트 코드 작성

package com.empering.springdemo.template;

import org.junit.jupiter.api.Test;

import java.io.IOException;

import static org.assertj.core.api.Assertions.assertThat;

public class CalculatorTest {
    @Test
    public void sumOfNumbers() throws IOException {
        Calculator calculator = new Calculator();
        int sum = calculator.calcSum(getClass().getResource("/numbers.txt").getPath());

        assertThat(sum).isEqualTo(10);
    }
}

 

A-2. 컴파일이 가능하도록 Calculator 클래스와 calcSum 메소드 생성 및 구현

package com.empering.springdemo.template;

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

public class Calculator {
    public int calcSum(String path) throws IOException {
        BufferedReader br = new BufferedReader(new FileReader(path));
        int sum = 0;
        String line = null;
        while ((line = br.readLine()) != null) {
            sum += Integer.parseInt((line));
        }

        br.close();
        return sum;
    }
}

 

A-3. 발생가능한 예외처리

package com.empering.springdemo.template;

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

public class Calculator {
    public int calcSum(String path) throws IOException {
        BufferedReader br = null;

        try {
            br = new BufferedReader(new FileReader(path));
            int sum = 0;
            String line = null;
            while ((line = br.readLine()) != null) {
                sum += Integer.parseInt((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());
                }
            }
        }
    }
}

 

B. 요구사항의 추가로 txt파일의 곱을 구하는 기능을 추가 (템플릿/콜백 패턴 적용)

템플릿에서 파일을 읽어 BufferedReader를 콜백에 전달해주고, 콜백이 각 라인을 읽어서 처리한 후 최종 결과를 템플릿에 리턴

 

B-1. 곱셈 기능을 위한 테스트 코드 추가 및 setUp 추가

package com.empering.springdemo.template;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.io.IOException;

import static org.assertj.core.api.Assertions.assertThat;

public class CalculatorTest {
    Calculator calculator;
    String numbersFilePath;

    @BeforeEach
    void setUp() {
        this.calculator = new Calculator();
        numbersFilePath = getClass().getResource("/numbers.txt").getPath();
    }

    @Test
    public void sumOfNumbers() throws IOException {
        assertThat(this.calculator.calcSum(this.numbersFilePath)).isEqualTo(10);
    }

    @Test
    public void multiplyOfNumbers() throws IOException {
        assertThat(this.calculator.calcMultiply(this.numbersFilePath)).isEqualTo(24);
    }
}

 

B-2. 곱셈기능 구현

package com.empering.springdemo.template;

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

public class Calculator {
    public int calcSum(String numbersFilePath) throws IOException {
        BufferedReader br = null;

        try {
            br = new BufferedReader(new FileReader(numbersFilePath));
            int sum = 0;
            String line = null;
            while ((line = br.readLine()) != null) {
                sum += Integer.parseInt((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 int calcMultiply(String numbersFilePath) throws IOException {
        BufferedReader br = null;

        try {
            br = new BufferedReader(new FileReader(numbersFilePath));
            int sum = 0;
            String line = null;
            while ((line = br.readLine()) != null) {
                sum *= Integer.parseInt((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());
                }
            }
        }
    }
}

기능은 정상적으로 작동하지만 중복코드가 많음

 

B-3. 템플릿/콜백 패턴 적용을 위한 콜백 인터페이스

package com.empering.springdemo.template;

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

public interface BufferedReaderCallBack {
    int doSomethingWithReader(BufferedReader br) throws IOException;
}

 

B-4. 콜백을 사용하는 템플릿 메소드

    public int fileReadTemplate(String numbersFilePath, BufferedReaderCallback callback) throws IOException {
        BufferedReader br = null;

        try {
            br = new BufferedReader(new FileReader(numbersFilePath));
            int resultValue = 0;
            resultValue = callback.doSomethingWithReader(br);
            return resultValue;
        } 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());
                }
            }
        }
    }

더하기, 곱하기의 코드 중 변하지 않는 부분만 추출 (try/catch/finally)

 

B-5. 템플릿/콜백을 적용한 calcSum(), calcMultiply()

    public int calcSum(String numbersFilePath) throws IOException {
        BufferedReaderCallback sumCallback = br -> {
            int sum = 0;
            String line = null;
            while ((line = br.readLine()) != null) {
                sum += Integer.parseInt((line));
            }
            return sum;
        };

        return fileReadTemplate(numbersFilePath, sumCallback);
    }

    public int calcMultiply(String numbersFilePath) throws IOException {
        BufferedReaderCallback sumCallback = br -> {
            int multiply = 0;
            String line = null;
            while ((line = br.readLine()) != null) {
                multiply *= Integer.parseInt((line));
            }
            return multiply;
        };

        return fileReadTemplate(numbersFilePath, sumCallback);
    }

 

C. calcSum과 calcMultiply의 중복되는 패턴이 존재

- 결과값 계산 시 +, * 부분만 다르고 나머지는 동일한 패턴

 

C-1. 라인별 계산 작업을 위한 콜백 인터페이스

package com.empering.springdemo.template;

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

 

C-2. LineCallback 인터페이스를 사용하는 템플릿

    public int lineReadTemplate(String numbersFilePath, LineCallback callback, int initValue) throws IOException {
        BufferedReader br = null;

        try {
            br = new BufferedReader(new FileReader(numbersFilePath));
            int resultValue = initValue;
            String line = null;
            while ((line = br.readLine()) != null) {
                resultValue = callback.doSomethingWithLine(line, resultValue);
            }
            return resultValue;
        } 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());
                }
            }
        }
    }

 

C-3. lineReadTemplate()를 사용하도록 calcSum(), calcMultiply() 수정

    public int calcSum(String numbersFilePath) throws IOException {
        LineCallback sumCallback = (line, value) -> {
            return value + Integer.parseInt(line);
        };

        return lineReadTemplate(numbersFilePath, sumCallback, 0);
    }

    public int calcMultiply(String numbersFilePath) throws IOException {
        LineCallback multiplyCallback = (line, value) -> {
            return value * Integer.parseInt(line);
        };

        return lineReadTemplate(numbersFilePath, multiplyCallback, 1);
    }

동일한 패턴의 중복을 제거해 코드가 간결해졌고, 순수한 계산로직만 있기 때문에 코드의 관심이 무엇인지 명확하게 보인다.

 

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

기존의 int 형태의 계산 뿐만아닌 다양한 오브젝트 타입의 처리를 위한 기능이 필요한경우

숫자 계산 뿐만아니라 문자를 합쳐주는 기능을 추가 요청

 

D-1. LineCallBack 인터페이스에 제네릭스 적용

package com.empering.springdemo.template;

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

 

D-2. LineCallback 인터페이스를 사용하는 템플릿에도 제네릭스 적용

    public <T> T lineReadTemplate(String numbersFilePath, LineCallback<T> callback, T initValue) throws IOException {
        BufferedReader br = null;

        try {
            br = new BufferedReader(new FileReader(numbersFilePath));
            T resultValue = initValue;
            String line = null;
            while ((line = br.readLine()) != null) {
                resultValue = callback.doSomethingWithLine(line, resultValue);
            }
            return resultValue;
        } 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());
                }
            }
        }
    }

 

D-3. calcSum(), calcMultiply() 수정

    public int calcSum(String numbersFilePath) throws IOException {
        LineCallback<Integer> sumCallback = (line, value) -> {
            return value + Integer.parseInt(line);
        };

        return lineReadTemplate(numbersFilePath, sumCallback, 0);
    }

    public int calcMultiply(String numbersFilePath) throws IOException {
        LineCallback<Integer> multiplyCallback = (line, value) -> {
            return value * Integer.parseInt(line);
        };

        return lineReadTemplate(numbersFilePath, multiplyCallback, 1);
    }

 

D-4. 문자열을 합쳐주는 기능의 테스트코드 추가

    @Test
    public void concatenateStrings() throws IOException {
        assertThat(this.calculator.concatenate(this.numbersFilePath)).isEqualTo("1234");
    }

 

D-5. concatenate() 기능 구현

    public String concatenate(String numbersFilePath) throws IOException {
        LineCallback<String> concatnateCallback = (line, value) -> {
            return value + line;
        };

        return lineReadTemplate(numbersFilePath, concatnateCallback, "");
    }

 

 

토비의 스프링 3.1 Vol. 1: 스프링의 이해와 원리, 에이콘출판 토비의 스프링 3.1 Vol. 2: 스프링의 기술과, 에이콘출판 토비의 스프링 3.1 세트:스프링의 이해와 원리 + 스프링의 기술과, 에이콘출판

 

토비의 스프링 3.1 세트:스프링의 이해와 원리 + 스프링의 기술과

COUPANG

www.coupang.com

“파트너스 활동을 통해 일정액의 수수료를 제공받을 수 있음"

반응형

댓글