스프링이 사랑한 디자인 패턴(4)

de_sj_awa·2021년 6월 5일
0
post-custom-banner

7. 전략 패턴(Strategy Pattern)

전략 패턴을 구성하는 세 요소는 다음과 같다.

  • 전략 메서드를 가진 전략 객체
  • 전략 객체를 사용하는 컨텍스트(전략 객체의 사용자/소비자)
  • 전략 객체를 생성해 컨텍스트에 주입하는 클라이언트(제3자, 전략 객체의 공급자)

클라이언트는 다양한 전략 중 하나를 선택해 생성한 후 컨텍스트에 주입한다.

군인이 있다고 상상해 보자. 그리고 그 군인이 사용할 무기가 있다고 하자. 보급 장교가 무기를 군인에게 지급해 주면 군인은 주어진 무기에 따라 전투를 수행하게 된다. 이 이야기를 전략 패턴에 따라 구분해 보면 무기는 전략이 되고, 군인은 컨텍스트, 보급 장교는 제3자, 즉 클라이언트가 된다. 이를 자바 코드로 구현해보자. 먼저 다양한 전략을 공통된 방식으로 사용하기 위해 인터페이스를 정의하자.

Strategy.java

package strategyPattern;

public interface Strategy {
    public abstract void runStrategy();
}

이제 다양한 전략, 즉 무기를 구현하자.

전략 인터페이스를 구현하는 StrategyGun.java

package strategyPattern;

public Class StategyGun implements Stategy {
    @Override
    public void runStrategy() {
        System.out.println("탕, 타당, 타다당");
    }
}

전략 인터페이스를 구현하는 StrategySword.java

package strategyPattern;

public Class StategySword implements Stategy {
    @Override
    public void runStrategy() {
        System.out.println("챙... 채재쟁 챙챙");
    }
}

전략 인터페이스를 구현하는 StrategyBow.java

package strategyPattern;

public Class StategyBow implements Stategy {
    @Override
    public void runStrategy() {
        System.out.println("슝.. 쐐액.. 쉑, 최종 병기");
    }
}

이번에는 무기(전략)을 사용할 군인(컨텍스트)을 구현하자.

전략을 사용하는 컨텍스트 Soldier.java

package strategyPattern;

public Class Soldier {
    void runContext(Strategy strategy) {
        System.out.println("전투 시작");
        strategy.runStrategy();
        System.out.println("전투 종료");
    }
}

마지막으로 무기(전략)를 조달(생성)해서 군인(컨텍스트)에게 지급(주입)해 줄 보급 장료(클라이언트, 제3자)를 구현하자.

전략 패턴의 클라이언트 Client.java

package strategyPattern;

public Class Client {
    public static void main(String[] args) {
        Strategy strategy = null;
        Soldier rambo = new Soldier();
        
        // 총을 람보에게 전달해서 전투를 수행하게 한다.
        strategy = new StrategyGun();
        rambo.runContext(strategy);
        
        System.out.println();
        
         // 검을 람보에게 전달해서 전투를 수행하게 한다.
        strategy = new StrategySword();
        rambo.runContext(strategy);
        
        System.out.println();
        
        // 활을 람보에게 전달해서 전투를 수행하게 한다.
        strategy = new StrategyBow();
        rambo.runContext(strategy);
    }
}

위 코드처럼 전략을 다양하게 변경하면서 컨텍스트를 실행할 수 있다. 전략 패턴은 디자인 패턴의 꽃이라고 할 정도로 다양한 곳에서 다양한 문제 상황의 해결책으로 사용된다. 또한 같은 문제의 해결책으로 상속을 이용하는 템플릿 메서드 패턴과 객체 주입을 통한 전략 패턴 중에서 선택/적용할 수 있다.

단일 상속만이 가능한 자바 언어에서는 상속이라는 제한이 있는 템플릿 메서드 패턴보다는 전략 패턴이 더 많이 활용된다.

전략 패턴을 한 문장으로 정리하면 다음과 같다.

"클라이언트가 전략을 생성해 전략을 실행할 컨텍스트에 주입하는 패턴"

전략 패턴의 클래스 다이어그램을 보면 개방 폐쇄 원칙(OCP)과 의존 역전 원칙(DIP)이 적용된 것을 짐작할 수 있다.

8. 템플릿 콜백 패턴(Template Callback Pattern - 견본/회신 패턴)

템플릿 콜백 패턴은 전략 패턴의 변형으로, 스프링의 3대 프로그래밍 모델 중 하나인 DI(의존성 주입)에서 사용하는 특별한 형태의 전략 패턴이다. 템플릿 콜백 패턴은 전략 패턴과 모든 것이 동일한데 전략을 익명 내부 클래스로 정의해서 사용한다는 특징이 있다. 앞에서 살펴본 전략 패턴 코드를 템플릿 콜백 패턴으로 바꿔보자. 익명 내부 클래스를 사용하기 때문에 StrategyGun.java, StrategySword.java, StrategyBow.java는 필요 없다.

전략 인터페이스 Strategy.java

package templateCallbackPattern;

public interface Strategy {
    public abstract void runStrategy();
}

전략을 사용하는 컨텍스트 Soldier.java

package templateCallbackPattern;

public Class Soldier {
    void runContext(Strategy strategy) {
        System.out.println("전투 시작");
        strategy.runStrategy();
        System.out.println("전투 종료");
    }
}

익명 내부 전략을 사용하는 클라이언트 Client.java

package templateCallbackPattern;

public Class Client {
    public static void main(String[] args) {
        Soldier rambo = new Soldier();
        
        rambo.runContext(new Strategy() {
            @Override
            public void runStrategy() {
            	System.out.println("총! 총조종총 총! 총!");
            }
        });
        
        System.out.println();
        
        rambo.runContext(new Strategy() {
           @Override
           public void runStrategy() {
                System.out.println("칼! 카카칼 칼! 칼!");
           }
       });
       
       rambo.runContext(new Strategy() {
           @Override
           public void runStrategy() {
                System.out.println("도끼! 독독...도도독 독끼!");
           }
       });
    }
}

그런데 코드를 보면 많은 부분에서 중복된 코드가 보인다. 리팩터링된 코드는 다음과 같다.

Strategy.java

package templateCallbackPattern;

public interface Strategy {
    public abstract void runStrategy();
}

인터페이스 코드는 동일하다.

Soldier.java

package templateCallbackPattern;

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

전략을 생성하는 코드가 컨텍스트, 즉 군인 내부로 들어왔다.

Client.java

package templateCallbackPattern;

public Class Client {
    public static void main(String[] args) {
        Soldier rambo = nwe Soldier();
        
        rambo.runContext("총! 총조종총 총! 총!");
        
        System.out.println();
        
        rambo.runContext("칼! 카카칼 칼! 칼!");
        
        System.out.println();
        
        rambo.runContext("도끼! 독독... 도도독 독끼!");
        
    }
}

클라이언트 코드가 상당히 깔끔해졌다. 중복되는 부분을 컨텍스트로 이관했기 때문이다. 스프링은 이런 형식으로 리팩터링된 템플릿 콜백 패턴을 DI에 적극 활용하고 있다. 따라서 스프링을 이해하고 활용하기 위해서는 전략 패턴과 템플릿 콜백 패턴, 리팩터링된 템플릿 콜백 패턴을 잘 기억해 두어야 한다. 마지막으로 템플릿 콜백 패턴을 한 문장으로 정리해 보자.

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

템플릿 콜백 패턴은 전략 패턴의 일종이므로 당연히 개방 폐쇄 원칙(OCP)와 의존 역전 원칙(DIP)이 적용된 설계 패턴이다.

참고

  • 스프링 입문을 위한 자바 객체지향의 원리와 이해
profile
이것저것 관심많은 개발자.
post-custom-banner

0개의 댓글