-
[디자인패턴] 템플릿 메소드(template-method) 패턴 & 템플릿 콜백(template-callback) 패턴Computer Basis/디자인패턴 및 설계이론 2022. 5. 22. 03:01
# 템플릿 메소드 패턴 (상속)
어떠한 로직을 서비 클래스가 확장할 수 있도록 템플릿으로 제공하는 방법
상위클래스(추상클래스)는 템플릿을 제공하고, 하위클래스는 구체적인 로직을 제공한다.
하위클래스에서 제공하는 로직이 상위클래스에서 그대로 호출이 되기 때문에
일종의 IoC(Inversion of Control) 라고 볼 수 있다.# 언제써야해?
- 코드가 몇줄만 다르고 거의 모든 부분이 동일할 때
- 실행순서를 보장하고 싶을 때
# 코드 예시
다음과 같은 FileProcessor 가 있다고 하자.
> FileProcessor.java
public abstract class FileProcessor { private String path; public FileProcessor(String path){this.path = path;} public int process(){ try(BufferedReader reader = new BufferedReader(new FileReader(path))){ int result = 0; String line = null; while((line = reader.readLine()) != null){ result += Integer.parseInt(line); } return result; } catch (IOException e){ throw new IllegalArgumentException("there's no file in path : " + path); } } }
지금은 단순히 라인의 수를 덧셈연산 하지만, 곱셈연산을 해달라는 요청이 있다고 가정하자.
그렇다면 10번째 줄만result *= Integer.parseInt(line)
바뀌면 되는데
이를 위해 다른 중복코드를 담아 processMultiply() 메서드를 생성하거나,
MutliplyFileProcessor 클래스를 만들어야 한다.
이렇게 소수의 변경사항 때문의 전체코드가 중복될 때 해당 패턴을 고려해봄직하다.> FileProcessor.java (Template Method Pattern - SuperClass)
중복라인을 추상메서드로 추출하여 하위클래스에게 Implementation 을 강제한다.
public abstract class FileProcessor { // 추상클래스로 new 키워드를 막아 하위클래스구현을 강제한다. private String path; public FileProcessor(String path){this.path = path;} public int process(){ try(BufferedReader reader = new BufferedReader(new FileReader(path))){ int result = 0; String line = null; while((line = reader.readLine()) != null){ result = getResult(result, Integer.parseInt(line)); } return result; } catch (IOException e){ throw new IllegalArgumentException("there's no file in path : " + path); } } // 하위클래스에서만 접근가능하도록 protected protected abstract int getResult(int result, int number); }
> Plus.java (Template Method Pattern - SubClass)
public class Plus extends FileProcessor{ public Plus(String path){super(path);} @Override protected int getResult(int result, int number) { return result +=number; } }
> Multiply.java (Template Method Pattern - SubClass)
public class Multiply extends FileProcessor{ public Multiply(String path){super(path);} @Override protected int getResult(int result, int number) { return result *=number; } }
> ClientSide
public class Client { public static void main(String[] args) { FileProcessor fileProcessor = new Plus("number.txt"); int result = fileProcessor.process(); System.out.println(result); } }
# 템플릿 콜백 패턴 (위임)
콜백으로 상속 대신 위임을 사용하는 탬플릿 패턴
> FileProcessor.java (Template Callback Pattern - Class)
public class FileProcessor { private String path; public FileProcessor(String path){this.path = path;} public int process(Operator operator){ try(BufferedReader reader = new BufferedReader(new FileReader(path))){ int result = 0; String line = null; while((line = reader.readLine()) != null){ result = operator.getResult(result, Integer.parseInt(line)); } return result; } catch (IOException e){ throw new IllegalArgumentException("there's no file in path : " + path); } } }
> Operator.java (Template Callback Pattern - Interface)
public interface Operator{ abstract int getResult(int result, int number); }
> ClientSide
public class Client { public static void main(String[] args) { FileProcessor fileProcessor = new FileProcessor("number.txt"); int result1 = fileProcessor.process(new Operator() { @Override public int getResult(int result, int number) { return result+=number; } }); // 구현메서드가 1개일 때 int result2 = fileProcessor.process(((result, number) -> result+=number)); System.out.println(result1); System.out.println(result2); } }
# 템플릿 메소드 패턴의 장점
1. 템플릿 코드(부모)를 재사용하고 중복코드를 줄일 수 있다.
2. 템플릿 코드를 변경하지 않고 상속을 받아서 구체적 로직만 변경할 수 있다.# 템플릿 메소드 패턴의 단점
1. 리스코프 치환원칙을 위반할 수도 있다.
2. 알고리즘이 복잡할 수록 템플릿을 유지하기 어려워진다.# 리스코프 치환원칙
상속구조에서 상위클래스를 받은 하위클래스에 의해
상위클래스 동작의 의도가 바뀌면 안된다.
예제 코드에서는 하위클래스에서 process() 를 오버라이딩하여 재작성할 수 있다.
이는 final로 막을 수 있으나, 구현하기로 한 코드 stub은 막을 수 없다.
즉, 사용자가 코드에 대해 이해가 없으면 본 의도가 깨질 수 있다.# 알고리즘이 복잡할 수록 템플릿의 본래 의도를 유지하기 힘들어지는 예시
public abstract class FileProcessor { private String path; public FileProcessor(String path){this.path = path;} public int process(){ try(BufferedReader reader = new BufferedReader(new FileReader(path))){ int result = 0; String line = null; while((line = reader.readLine()) != null){ result = getResult(result, Integer.parseInt(line)); somethingDo(result); somethingDo2(result); result+=1; somethingDo3(result); } return result; } catch (IOException e){ throw new IllegalArgumentException("there's no file in path : " + path); } } protected abstract void somethingDo(int result); protected abstract void somethingDo2(int result); protected abstract void somethingDo3(int result); protected abstract int getResult(int result, int number); }
구현해야할 코드 stub 이 많아질수록, 또 그 사이에 끼여져있는 로직이 많아질 수록
본래 의도와 다르게 로직이 진행될 수 있다.
# 템플릿 메서드 패턴 또 다른 예시
public abstract class Car { //overrding 가능 protected void turnOn(){ System.out.println("시동을 켠다"); }; //하위 클래스에 delegate (overriding 필수) public abstract void drive(); //overrding 가능 protected void turnOff(){ System.out.println("시동을 끈다."); } //HookMethod (필요하다면 재정의해서 사용할 수 있음, 재정의하지 않으면 그냥 넘어가는 메서드들) public void beforeTurnOn(){}; public void afterTurnOn(){}; public void beforeTurnOff(){}; public void afterTurnOff(){}; // 템플릿 메서드 (overriding 불가) public final void driving(){ beforeTurnOn(); turnOn(); afterTurnOn(); drive(); beforeTurnOff(); turnOff(); afterTurnOff(); } }
> Spring Filter
여기에 대해서 할 말이 있는데,
필자가 처음 Filter에 대해 잘 몰랐을 때
[API 호출전 로직] 부분에 if(condition){chain.doFilter(req,res); } 를 호출 했었다.
if(condition){chain.doFilter(req,res); } 과
if(condition){chain.doFilter(req,res); return;} 의 차이는 엄청 크다.
즉, 클라이언트 입장에서는 라이브러리 이해도에 따라 기대하는 동작이 다르게 나올 수 있다.@Component public class TestFilter implements Filter{ @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // API 호출 전 로직 chain.doFilter(request, response); // API 호출 후 로직 } }
[Spring Security] Filter 에 대한 오해와 진실 (doFilter의 정확한 작동방식에 대한 심층분석과 Filter의 흐
# [만연한 오해] doFilter() 기준으로 위쪽이 API 호출 전처리 과정이고, 아래쪽이 API 호출 후처리 과정이다. (틀린 말은 아니나 상황에 따라 아닌 경우가 있으므로, 이에대해 확실히 알고 넘어갈 필
developer-ping9.tistory.com
728x90'Computer Basis > 디자인패턴 및 설계이론' 카테고리의 다른 글
[디자인 패턴] 방문자 패턴(Visitor Pattern) (0) 2022.06.04 [디자인패턴] 브릿지 패턴 (Bridge Pattern) (0) 2022.05.08 [설계이론] Project Structure - 프로젝트 구성을 어떻게 설계해야 할까? (패키지 분리기준) (0) 2022.03.27 [디자인패턴] 싱글톤패턴을 쓰는 이유와 주의할 점 (0) 2022.03.15 [디자인패턴] 상속보다는 컴포지션을 사용하자!!! (0) 2022.02.17