OOP

템플릿 메서드 / 전략 / 템플릿 콜백 패턴 차이

짜비 2022. 9. 18. 19:33

템플릿 메서드 패턴

설명

  • 코드에 변하는 부분과 변하지 않는 부분이 있을 때, 변하지 않는 부분을 추상 클래스 내 메서드로 정의하고, 변하는 부분을 추상 클래스 내 abstract 메서드로 정의하여 자식 클래스에서 변하는 부분을 abstract 메서드를 override 하여 구현하는 패턴.

코드 예시

public abstract class AbstractTemplate {

    public void execute() {
        long startTime = System.currentTimeMillis();
        //비즈니스 로직 실행
        call();
        //비즈니스 로직 종료
        long endTime = System.currentTimeMillis();
        long resultTime = endTime - startTime;
        log.info("resultTime={}", resultTime);
    }

    protected abstract void call();

}

public class SubClassLogic1 extends AbstractTemplate{
    @Override
    protected void call() {
        log.info("비즈니스 로직1 실행");
    }
}

public class SubClassLogic2 extends AbstractTemplate{
    @Override
    protected void call() {
        log.info("비즈니스 로직2 실행");
    }
}

단점

  • 상속을 사용한다.
    • 자식 클래스는 부모 클래스(abstract class) 의 기능을 전혀 사용하지 않지만 부모 클래스의 코드를 가지고 있다.
    • 별도의 자식 클래스 혹은 익명내부클래스를 정의해야 한다. -> 번거롭다.

전략 패턴

설명

  • 변하지 않는 부분을 context 에 정의하고, 변하는 부분을 Strategy 라는 인터페이스를 정의 && 이 인터페이스를 구현하는 방식으로 작성한다.
  • context 내 필드 값으로 Strategy 인터페이스를 가지고 있고, 이곳에 구체 Strategy 클래스를 전달해 변하는 부분을 처리한다.
    • -> 스프링이 DI 시 사용하는 방식!

코드 예시

public class ContextV1 {
    private Strategy strategy;

    public ContextV1(Strategy strategy) {
        this.strategy = strategy;
    }

    public void execute() {
        long startTime = System.currentTimeMillis();
        //비즈니스 로직 실행
        strategy.call();    //위임
        //비즈니스 로직 종료
        long endTime = System.currentTimeMillis();
        long resultTime = endTime - startTime;
        log.info("resultTime={}", resultTime);
    }
}


public interface Strategy {
    void call();
}


public class StrategyLogic1 implements Strategy{
    /*
    전략패턴 장점 : 템플릿-메소드 패턴과 달리 Interface 만을 구현 -> 부모 클래스 코드를 가지고 있지 않다. -> 부모 클래스 변경으로부터 자유로움.
    이펙티브 자바, 상속보다 위임!
     */

    @Override
    public void call() {
        log.info("비즈니스 로직1 실행");
    }
}

public class StrategyLogic2 implements Strategy{
    @Override
    public void call() {
        log.info("비즈니스 로직2 실행");
    }
}
public class ContextV1Test {

    @Test
    void templateMethodV0() {
        logic1();
        logic2();

    }

    private void logic1() {
        long startTime = System.currentTimeMillis();
        //비즈니스 로직 실행
        log.info("비즈니스 로직1 실행");
        //비즈니스 로직 종료
        long endTime = System.currentTimeMillis();
        long resultTime = endTime - startTime;
        log.info("resultTime={}", resultTime);
    }

    private void logic2() {
        long startTime = System.currentTimeMillis();
        //비즈니스 로직 실행
        log.info("비즈니스 로직2 실행");
        //비즈니스 로직 종료
        long endTime = System.currentTimeMillis();
        long resultTime = endTime - startTime;
        log.info("resultTime={}", resultTime);
    }

    @Test
    void strategyV1() {
        StrategyLogic1 strategyLogic1 = new StrategyLogic1();
        ContextV1 context1 = new ContextV1(strategyLogic1);
        context1.execute();

        StrategyLogic2 strategyLogic2 = new StrategyLogic2();
        ContextV1 context2 = new ContextV1(strategyLogic2);
        context2.execute();
    }

    @Test
    void strategyV2() {
        Strategy strategyLogic1 = new Strategy() {
            @Override
            public void call() {
                log.info("비즈니스 로직1 실행");
            }
        };
        ContextV1 context1 = new ContextV1(strategyLogic1);
        log.info("strategyLogic1={}", strategyLogic1.getClass());
        context1.execute();

        Strategy strategyLogic2 = new Strategy() {
            @Override
            public void call() {
                log.info("비즈니스 로직1 실행");
            }
        };
        ContextV1 context2 = new ContextV1(strategyLogic1);
        log.info("strategyLogic2={}", strategyLogic2.getClass());
        context2.execute();

    }

    @Test
    void strategyV3() {

        ContextV1 context1 = new ContextV1(new Strategy() {
            @Override
            public void call() {
                log.info("비즈니스 로직1 실행");
            }
        });
        context1.execute();

        ContextV1 context2 = new ContextV1(new Strategy() {
            @Override
            public void call() {
                log.info("비즈니스 로직1 실행");
            }
        });
        context2.execute();

    }

    @Test
    void strategyV4() {
        ContextV1 context1 = new ContextV1(() -> log.info("비즈니스 로직1 실행"));
        context1.execute();

        ContextV1 context2 = new ContextV1(() -> log.info("비즈니스 로직2 실행"));
        context2.execute();
    }

}

단점

  • contextstrategy 를 조립한 이후에는 전략을 변경하기가 번거롭다.

템플릿 콜백 패턴

설명

  • 전략 패턴과 다르게, Strategy 를 필드에 가지고 있지 않고, 메서드 파라미터로 넘겨받는 방식.
  • Context 를 실행할 때마다 Strategy 를 변경할 수 있다. 즉, 일반적인 전략패턴보다 유연하게 전략 변경이 가능하다.
  • 변하지 않는 부분 : 템플릿
  • 변하는 부분(전략) : 콜백 -> ‘실행 가능한 코드 조각’ 을 의미.

코드 예시

public class TimeLogTemplate {

    public void execute(Callback callback) {
        long startTime = System.currentTimeMillis();
        //비즈니스 로직 실행
        callback.call();    //위임
        //비즈니스 로직 종료
        long endTime = System.currentTimeMillis();
        long resultTime = endTime - startTime;
        log.info("resultTime={}", resultTime);
    }

}

public interface Callback {
    void call();
}


public class TemplateCallbackTest {

    /**
     * 템플릿 콜백 패턴 - 익명 내부 클래스
     */
    @Test
    void callbackV1() {
        TimeLogTemplate template = new TimeLogTemplate();
        template.execute(new Callback() {
            @Override
            public void call() {
                log.info("비즈니스 로직1 실행");
            }
        });

        template.execute(new Callback() {
            @Override
            public void call() {
                log.info("비즈니스 로직2 실행");
            }
        });
    }

    /**
     * 템플릿 콜백 패턴 - 익명 내부 클래스, 람다
     */
    @Test
    void callbackV2() {
        TimeLogTemplate template = new TimeLogTemplate();
        template.execute(() -> log.info("비즈니스 로직1 실행"));
        template.execute(() -> log.info("비즈니스 로직2 실행"));
    }
}

출처