소스 코드를 기록하는 남자

템플릿 콜백 패턴(Template Callback Pattern)

디자인 패턴

템플릿 콜백 패턴은 전략 패턴의 변형이며 스프링 3대 프로그래밍 모델 중 하나인 DI (의존성 주입)에서 사용하는 특별한 형태의 전략 패턴이다. 템플릿 콜백 패턴은 전략 패턴과 모든 것이 동일하나 전략을 익명 내부 클래스로 정의해서 사용하는 것이 차이이다. 앞에서 봤던 내용을 템플릿 콜백 패턴으로 변경해보자. 

 

package 전략패턴;

public class Client {
    public static void main(String[] args) {
        전략 strategy = null;
        군인 rambo = new 군인();

        strategy = new 총();
        rambo.runContext(strategy);

        strategy = new 칼();
        rambo.runContext(strategy);

        strategy = new 활();
        rambo.runContext(strategy);

    }
}

 

위의 코드는 기존의 코드이다. 익명 내부 클래스를 사용하여 변경해보자.

 

 

package 전략패턴;

public class Client {
    public static void main(String[] args) {
        군인 rambo = new 군인();

        rambo.runContext(new 전략() {
        	@Override
            public void runStrategy() {
            	System.out.println("총 : 빵야");
            }
        });

        rambo.runContext(new 전략() {
        	@Override
            public void runStrategy() {
            	System.out.println("칼 : 슈욱");
            }
        });
        
                rambo.runContext(new 전략() {
        	@Override
            public void runStrategy() {
            	System.out.println("활 : 슈슉");
            }
        });

    }
}

 

위와 같이 익명 내부 클래스로 변경했다. 코드를 보자하니 중복되는 부분이 많다. 이를 리팩토링 해보자.

 

package 전략패턴;

public class 군인 {
    void runContext(String weaponSound) {
        System.out.println("전투 시작");
        executeWeapon(weaponSound).runStrategy();
        System.out.println("전투 종료");
    }
    
    private 전략 executeWeapon(final String weaponSound) {
        return new 전략() {
            @Override
            public void runStrategy() {
                System.out.println(weaponSound);
            }
        };
    }
}

public class Client {
    public static void main(String[] args) {
        군인 rambo = new 군인();

        rambo.runContext("총");

        rambo.runContext("칼");

        rambo.runContext("활");

    }
}

아래와 같이 리팩토링이 가능하다. 리팩토링의 예제를 보면 볼수록 신기하고 즐겁다.

이렇게 리팩토링하여 보니, 전략이 군인의 내부로 들어왔다. 스프링은 이와 같은 형식으로 템플릿 콜백 패턴을 DI에 적극 활용하고 있다. 따라서 전략 패턴과 템플릿 콜백 패턴, 리팩터링된 템플릿 콜백 패턴은 꼭 기억해둘 필요성이 있다.

 

마지막 한마디로 정리해보자.

 

"전략을 익명 내부 클래스로 구현한 전략 패턴"

 

guy-who-writes-sourcecode.tistory.com/30

 

스프링에 녹아있는 아름다운 디자인 패턴

프로그램을 개발하다 보면 많은 상황에 직면하게 되는데, 프로그래밍의 역사가 꽤 길지 않은가? 따라서 이와 비슷한 사례를 이미 경험한 선배들이 정리해 둔 표준 설계 패턴이 있다. 이를 디자

guy-who-writes-sourcecode.tistory.com

 

템플릿 메소드 패턴 (Template Method Pattern)

디자인 패턴

템플릿 메소드는 템플릿을 제공하는 메소드, 하위 클래스에게 구현을 강제하는 추상 메소드, 하위 클래스가 선택적으로 오버라이딩할 수 있는 Hook 메소드를 두는 패턴을 템플릿 메소드 패턴이라 한다.

 

이해가 되지 않는다면, 코드를 보는 것이 좋다.

 

억지스럽게 코드를 한번 작성해보겠다. 너무 불편해하지 않았으면 좋겠다.

 

public class Bmw {
    public void driveOnRoad() {
    	System.out.println("자동차 시동 부릉");
        System.out.println("수동 기어로 시작");
        System.out.println("정지");
        System.out.println("자동차 시동 끄기");
    }
 }
 
 public class Audi {
    public void driveOnRoad() {
    	System.out.println("자동차 시동 부릉");
        System.out.println("자동 기어로 시작");
        System.out.println("정지");
        System.out.println("자동차 시동 끄기");
    }
 }

위와 같은 코드가 있다고 하자. 객체 지향의 특징을 조금이나마 이해하고 있다면, 위에서 반복적하는 코드에 대한 리팩토링 의지가 불타오를 것인다. 따라서 이를 템플릿 메소드 패턴을 적용하여 개선해보자.

 

package 템플릿메소드패턴;

public abstract class Car {
    public void driveOnRoad() {
        System.out.println("자동차 시동 부릉");
        drive();
        stop();
        System.out.println("자동차 시동 끄기");
    }
    
    abstract drive();
    
    void stop() {
    	System.out.println("정지");
    }
}
        
        
package 템플릿메소드패턴;

public class Bmw extends Car {
    @Override
    void drive() {
    	System.out.println("자동 주행");
    }
    
    @Override
    void stop() {
    	System.out.println("Bmw 정지");
    }
 }
 
 
package 템플릿메소드패턴;

public class Audi extends Car {
    @Override
    void drive() {
    	System.out.println("수동 주행");
    }
    
    @Override
    void stop() {
    	System.out.println("Audi 정지");
    }
 }

 

코드만 봐서 이해가 안될 것인다. 하나 하나 짚어서 설명해보겠다.

 

템플릿 메소드 패턴 구성 요소 상위 클래스 Car 하위 클래스 (Bmw, Audi)
템플릿 메소드는 공통 로직을 수행하는 부분, 공통 로직 안에서 하위 클래스에서 오버라이딩한 추상 메소드/훅 메소드를 호출 driveOnRoad()  
템플릿 메소드에서 호출하는 추상 메소드, 하위 클래스가 반드시 오버라이딩하도록 만든다. drive() 오버라이딩 필수
템플릿 메소드에서 호출하는 훅 메소드를 하위 클래스에서 선택적으로 오버라이딩합니다. stop() 오버라이딩 선택

 

"상위 클래스의 견본 메서드에서 하위 클래스가 오버라이딩한 메소드를 호출하는 패턴"

 

템플릿 메소드 패턴이 의존 역전 법칙(DIP)을 활용하고 있음을 알 수 있다. 이 패턴을 통해서 중복되는 공통 로직을 리팩토링하고, 개별로 다르게 진행되는 로직은 추상 메소드와 훅 메소드를 사용하여 강제로 오버라이딩하거나 선택적으로 오버라이딩할 수 있습니다.

 

guy-who-writes-sourcecode.tistory.com/30

 

데코레이터 패턴(Decorator Pattern)

디자인 패턴

장식하는 사람이란 입장에서 접근해보자. 데코레이터 패턴의 구현 방법은 프록시 패턴과 동일하다. 다만 프록시 패턴과 다른 점은 프록시 패턴이 반환값을 조작하지 않고 그대로 전달하는 것과 다르게 데코레이션을 한다.

 

바로 코드를 확인해보자.

 

[IService]

package decoratorPattern;

public interface IService {
    public abstract String runProcess();
}

[Service]

package decoratorPattern;

public class Service implements IService {
    @Override
    public String runProcess() {
        return "process";
    }
}

[Decorator]

package decoratorPattern;

public class Decorator implements IService {
    IService service;

    @Override
    public String runProcess() {
        System.out.println("호출에 대한 장식이 주목적이며, 클라이언트에게 장식이 달린 반환 결과를 전달");
        service = new Service();
        return "장식" + service.runProcess();
    }
}

 

기존의 프록시 패턴과 다르게 Decorator의 runProcess가 반환하는 문자열에 "장식" 이 추가되지 않았는가?

이제 데코레이터 패턴의 핵심들을 살펴보자.

 

  • 장식자는 실제 서비스와 같은 이름의 메소드를 사용한다.

  • 장식자는 실제 서비스에 대한 참조 변수를 갖는다 (합성).

  • 장식자는 실제 서비스와 같은 이름을 가진 메소드를 호출하고, 반환값에 장식을 붙여서
    클라이언트에게 전달한다.

  • 장식자는 실제 서비스의 메소드 호출 전후에 별도의 로직을 수행할 수 있다.

 

하나만 기억하자!

메소드 호출 반환값에 변화를 주기 위해 중간에 데코레이터를 두는 패턴!

 

스프링에 녹아있는 디자인 패턴들

guy-who-writes-sourcecode.tistory.com/30