본문 바로가기
디자인 패턴

Template Pattern(템플릿 패턴) VS Strategy Pattern(전략 패턴)

by simplify-len 2020. 9. 27.

Photo by Mike Meyers on Unsplash

들어가기

대부분의 디자인 패턴 책에서는 이 두가지를 비교해서 설명합니다. 왜 일까요? 이 두가지 패턴은 데이터를 은닉화 시켜 구현될 수 있도록 도와주는 패턴입니다.

각각의 쓰이는 상황은 다르지만, 결론부터 이야기하면 잘 짜여진 코드는 템플릿 패턴에서 전략 패턴으로 손쉽게 전환할 수 있고, 반대로 전략패턴에서 템플릿 패턴으로 손쉽게 변경가능하다고 합니다.

그럼 왜 그런지 한번 살펴봅시다.

템플릿 패턴이란 무엇인가?

하위 클래스에서 구체적으로 처리하는 디자인 패턴

 개인적으로 패턴을 어렵게 생각하면 끝없이 어렵고 쉽게 생각하면 너무 쉽게 생각하는 경향이 있다고 생각합니다. 그런 예시로 템플릿 패턴도 포함됩니다.

부주제로  '하위 클래스에서 구체적으로 처리하는 디자인 패턴' 이라 칭했습니다.

말 그대로, 하위 클래스가 상위 클래스의 구현되지 못한 부분을 구현 시켜 주는 것. 이것입니다. 사실 이게 끝이지만, 이게 끝이라면 템플릿 패턴을 모두다 이해했다고 말하기 힘듭니다.

여기서 우리가 알아야할 것이 있습니다. 바로 의존성의 방향입니다. 

대부분 뼈를 구성하는 인터페이스를 만들게 되면 의존성의 방향은 아래 그림과 같이 됩니다.

그러나, 템플릿 패턴을 활용하게 된다면, 아래 그림과 같이 의존성이 역전됩니다.

이는 SOLID 원칙 중에서 D에 해당하는 의존성 역전 법칙과 관련되 있습니다. 의존성 역전 법칙의 자세한 설명은 아래 링크를 참조해 주시고, 의존 역전 법칙이 무엇인지 위에서 간단히 위에 설명했습니다.

https://walbatrossw.github.io/oop/2018/07/27/06-solid-dip.html

 

SOLID - 의존 역전 원칙(Dependency Inversion Principle) - 더블에스 Devlog

본 글은 자바 객체지향과 디자인패턴를 읽고 개인적으로 학습한 내용을 복습하기 위해 작성된 글로 내용상 오류가 있을 수 있습니다. 오류가 있다면 지적 부탁드리겠습니다. 1. 의존역전 원칙이

walbatrossw.github.io

 그럼 코드로서 템플릿 패턴을 살펴봅시다.

템플릿 패턴 > 코드로서 표현하기

https://github.com/LenKIM/design-pattern/tree/master/template/src/main/java

 

LenKIM/design-pattern

Contribute to LenKIM/design-pattern development by creating an account on GitHub.

github.com

import java.util.HashSet;
import java.util.Set;

/**
 * 템플릿 메서드 패턴 설명
 */
abstract class DiscountPolicyTemplate {

    private Set<DiscountCondition> conditions = new HashSet<>();


    public void addCondition(DiscountCondition condition) {
        conditions.add(condition);
    }

    public Money calculateFee(Screening screening, int count, Money fee) {
        for (DiscountCondition condition : conditions) {
            if (condition.isSatisfiedBy(screening, count)) {
                return calculateFee(fee);
            }
        }
        return fee;
    }

    protected abstract Money calculateFee(Money fee);
}

여기서 calculateFee 의 구현에 따라 DiscountPolicyTemplate.class의 역할이 달라집니다. 그러므로, DiscountPolicyTemplate.java 의 구현체는 calculateFee에 의존하게 됩니다.

아래 DiscountPolicyTemplate 을 구현한 클래스 입니다.

public class AmountPolicy extends DiscountPolicyTemplate {
    private final Money amount;

    public AmountPolicy(Money amount) {
        this.amount = amount;
    }

    @Override
    public Money calculateFee(Money fee) {
        return fee.minus(amount);
    }
}

@Override 로 보면 알 수 있듯이, 의존성 방향이 DiscountPolicyTemplate 으로 흘러간다는 사실을 알 수 있습니다.

그렇다면, 전략패턴은 무엇일까요?

전략이라는 의미는, 적을 쳐부시기 위한 작선, 군대를 움직일 때의 방책 그리고 문제를 해결해 갈 때의 방법 등의 의미를 가지고 있습니다. 프로그래밍의 경우에는 '알고리즘' 을 말합니다.

 전략 패턴에서는 그 알고리즘을 구현한 부분을 모두 교환할 수 있습니다. 알고리즘(전략, 작전, 방책)을 빈틈없이 교체해서 같은 문제를 다른 방법으로도 쉽게 해결할 수 있도록 도와주는 패턴이 전략 패턴입니다.

코드로서의 전략 패턴

 그럼, 이제 코드로서 전략 패턴을 위 코드에서 조금의 수정으로 변경되는 것을 보여드리겠습니다.

public class DiscountPolicyStrategy {

  private final Set<DiscountCondition> conditions = new HashSet<>();
  private final Calculator calculator;
  
  public DiscountPolicy(Calculator calculator){
  	this.calculator = calculator;
  } 
  public void addCondition(DiscountCondition condition { 
    conditions.add(condition);
  } 
  public Money calculateFee(Screening screening, int count, Money fee){
    for(DiscountCondition condition:conditions){
      if(condition.isSatisfiedBy(screening, count)) 
        return;
    }
    return fee; 
  }
}

템플릿 패턴에서 보였던 DiscountPolicy 에서 변형된 형태입니다. 4번 라인의 Calculator 가 추가되었습니다. 

public class AmountCalculator implements Calculator {

    private final Money amount;

    public AmountCalculator(Money amount) {
        this.amount = amount;
    }

    @Override
    public Money calculateFee(Money fee) {
        return fee.minus(amount);
    }
}
public interface Calculator {
    Money calculateFee(Money fee);
}

템플릿 패턴과 결과적으로는 같지만, 과정이 다릅니다. 전략 패턴의 경우, Calculator 에게 일부 책임을 위임해 비지니스 로직을 완성합니다.

이 둘의 공통점은 무엇일까요?

 글의 맨 처음에 언급했던 것과 같이 비지니스 로직을 은닉화를 한다는 부분에 대해서는 공통점을 가지고 있습니다. 또한 의존관계를 역전시켜 확장성있는 코드를 만들 수 있게 도와줍니다.

이 둘의 차이점은 무엇일까요?

위 그림에서 보여주듯이, 의존성의 방향이 다릅니다. 왼쪽의 경우 전략 패턴을 활용한 부분이고, 오른쪽은 탬플릿 패턴을 활용한 의존성의 방향입니다.

전략 패턴의 경우, Calculator가 완충역할을 하는 반면에, 템플릿메서드 패턴의 경우에는 의존성 뱡향을 변경합니다.

중간에 Calculator 가 있기 때문에, 변화가 많이 일어날 경우 도움이 될 수 있다. 그러나 Calculator가 무거워 질 수 있습니다.

또한 사용하는데 있어서도 문제점이 있습니다.

 이 두가지를 상황에 맞쳐서 사용하면 되는데, 이 둘에게도 문제점이 존재합니다.

템플릿 패턴의 경우에는 조합폭발이 일어날 경우,, 그만큼 class를 만들어야 한다. 그럴 경우 구제가 불가능한 상황이 맞이합니다.

반대로, 전략패턴은 중간에 쿠셔닝할 수 있는 객체를 둔다는 관점에서 의존성 폭발이 일어날 수 있다.

 

[참고자료]

댓글