프로그램 아키텍쳐/디자인패턴

스트래티지 패턴 ( Strategy pattern )

본클라쓰 2009. 1. 2. 17:21

 소프트웨어는 개발한 후 유지·보수에 시간이 많이 투자하게 된다. 이 때 소프트웨어에 새로운 기능을 추가하거나, 다른 기능을 추가시켜야 하는 문제를 해결해야 한다. 하지만 소스 코드를 대거 수정하는 방법을 택한다면 혼란스러울 수 있다. 이럴 때 "변화 하는 부분"과 "고정되어 있는 부분"에 대한 문제를 다룬 것이 스트래티지(Strategy) 패턴이다. 스트래티지 패턴은 일종의 캡슐화이다. 

 

 보통 객체 지향 프로그램에서는 업그레이드 방법을 상속을 통해 한다. 그런데 상속은 필요 없는 부분까지 같이 상속이 되어 필요없는 데이터나가 기능까지 작용하는 문제가 발생한다. 즉, 상속의 단점은 서브 클래스에서 코드가 중복되고, 코드를 변경할 시 다른 객체에도 영향을 미치며, 실행시에 특징을 바꾸기 어렵고, 모든 객체의 특징을 알기가 어렵다.

 

 이런 상속의 문제점을 인터페이스를 이용하여 변화시켜야 할 기능을 상속받아 구현시키게 만들면 문제가 해결될까 생각할 수도 있다. 하지만 특정 행동에 대해 메소드 오버라이드를 통해 구현해야 하는 인터페이스는 매번 객체에 대해 특정 행동을 구현해줘야 하기 때문에 코드 재사용을 기대하기 힘들어 코드 관리면에서 또 다른 문제에 부딛치게 된다.  

 

 바로 이런 상황에서 사용되는 것이 스트래티지 패턴(Strategy Pattern)이다. 스트래티지 패턴의 핵심은 '바뀌는 부분을 따로 뽑아서 캡슐화 시킨다'이다. 그렇게 하여 나중에 바뀌지 않는 부분에는 영향을 미치지 않은 채로 그 부분만 고치거나 확장할 수 있다. 

  

 즉, 슈퍼클래스에서 바뀌는 부분을 따로 분리하여 클래스화 시킨다. 그렇게 바뀌는 부분을 따로 분리하여 인터페이스를 사용하여 서블 클래스으에서 정의한다. 그리고 구체적으로 구현된 서브 클래스를 대입시켜 사용하는 것이다. 

 

 예를 들어 비행기를 생각해 보자. 비행기는 제트방식, 프로펠러 방식 등 여러가지 방식으로 비행하는 방법이 있다. 이런 비행하는 방법은 여러 가지가 되고 비행기마다 방식이 다르다. 비행하는 방식으로 모아서 캡슐화시켜 인터페이스 구현을 통해 틀을 잡는다. 

 

 

인터페이스(캡슐화)  

/** 최상의 인터페이스 클래스로 바뀌는 부분을 정의해 놓음 */ 

public interface HowFly {

 

    /* 비행방법을 정의하기 위한 메소드(구현하는 클래스는 반드시 정의 해야함 */

    public void howFly();   

}

 

제트 방식 비행기

/** 제트방식을 구현 */ 

public class Jet implements HowFly {


    public void howFly() {
        System.out.println("제트방식");
    }
}

 

[ 프로펠러 방식 ]

/** 프로펠러 방식을 구현 */ 

public class Propeller implements HowFly { 


    public void howFly() {
        System.out.println("프로펠터방식");
    }
}

 

 이렇게 인퍼테이스로 변화하는 부분을 따로 정의 했다면 캡슐화에 성공한 것이다. 이젠 캡슐화한 클래스를 어떻게 사용하는 가를 알아보겠다.

 

 

기본적인 최상위 비행기 클래스를 작성한다. 

/** 비행기 기본 클래스 */
public class Plane {


    HowFly howfly;    /* 비행 방법을 정의해 놓은 인터페이스 클래스 변수 선언 */
 
    public void performFly(){    /* 비행 방법을 결정하는 메소드 */
        howfly.howFly();
    }
}

 

이렇게 인터페이스 캡슐화시킨 클래스를 "has a"상속을 통해 사용한다. 결국에서 어떤 방법을 선택하는냐의 문제만 남게 된다.

 

 

이제 실제 사용 객체를 만들어 보겠다. 

/** Plane001클래스는 Plane클래스에서 상속받음 */ 

public class Plane001 extends Plane { 


    public Plane001() {
        howfly = new Jet();    /* 비행방법 구현한 클래스 중 제트 방식으로 생성 */
    }
}

 

 이런 방법으로 사용하면 쉽게 기능을 사용 할 수 있다. 또한 새로운 기능을 추가해야 할 때도 인터페이스 클래스만 수정하면 되므로 코드의 재사용성도 높일 수 있다. 이런 방식이 정적으로 행동을 지정하는 방법이다.

 

 

이제 동적으로 행동을 지정하는 방법을 살펴보자.

 

 생성자에서 인스턴스(객체)를 만들어서 사용하는 방법이 아니라 비행기의 서브클래스에서 세터 메소드를 호출하는 방법으로 설정하는 방법이다.

 

/** 비행기 클래스에 동적으로 행동을 지정하기 위한 메소드 삽입 */ 

public class Plane {


    HowFly howfly;
 
    public void performFly() {
        howfly.howFly();
    }
 

    /* 동적으로 행동을 지정하기 위한 메소드 삽입 (전달인자로 필요한 기능으로 받음)*/
    public void setPerformFly(HowFly hf) {    
        howfly = hf;
    }
}

 

set메소드를 구현하여 필요한 기능을 전달인자로 받음으로써 문제를 해결했다.  

 

 

실제 사용한 클래스를 보면 명확하게 알 수 있다.  

/** 동적으로 행동을 지정하는 방법을 사용한 테스트 메인함수 */ 

public class Simulator {

 

    public static void main(String[] args) {


        Plane p = new Plane001();    /* 일반적인 사용방법 */
        p.performFly();
        p.setPerformFly(new Propeller());   /* 동적으로 행동을 지정하여 삽입 */
        p.performFly();
    }
}