디자인 패턴 - strategy pattern

Programming/Design Pattern 2012. 2. 10. 21:46

안녕하세요 .
블로그 포스팅을 아무렇게나 이것저것 마구잡이로 하고 있는 nolleh 입니다 .

이번엔 요새 스터디를 하고 있는 디자인 패턴에 관해 정리해볼까합니다 .
(안드로이드와 - 영상처리도 업로드해야하는데 ,.. 영상처리는.... 맷랩도 깔려있지 않아서야 .. ;;; )
책을 보고 공부하는데 책의 내용을 내것으로 만들어서 이를 나중에 필요할때마다 다시 읽으며 곱씹어보기위해 정리하려합니다.

저도 아직 배우는 입장이기 때문에 모든 정보가 맞다고는 보장할 수 없음을 양해해 주시기 바라며 ,
잘못된 정보가 있다면 지적해주세요 . 정정해나가겠습니다. ^^ 


1. 디자인 패턴 ?

프로그래머들이 많이들 이야기하는 객체지향적 프로그래밍이 유지보수등에 매우 중요한 역할을 하지만,
추상화, 캡슐화, 은닉, 상속, 다형성 등등만의 지식만으로는 해결되지 않는 많은 설계적 상황이 존재합니다 .
우리가 고민할 법한 이런 상황들을 많은 경험을 쌓으신 선임프로그래머(? 적당한 표현이 생각나지 않네요) 들이 패턴화 해서 알려진 것들이 디자인 패턴이라고 합니다.

Definition!
소프트웨어 개발 방법에서 사용되는 디자인 패턴은, 프로그램 개발에서 자주 나타나는 과제를 해결하기 위한 방법 중 하나로, 과거의 소프트웨어 개발 과정에서 발견된 설계의 노하우를 축적하여 이름을 붙여, 이후에 재이용하기 좋은 형태로 특정의 규약을 묶어서 정리한 것이다. 알고리즘과 같이 프로그램 코드로 바로 변환될 수 있는 형태는 아니지만, 특정한 상황에서 구조적인 문제를 해결하는 방식을 설명해 준다.


예컨데  ,
코드의 중복성을 제거하기 위해 ( 중복성은 가장 우선시 되는 제거 대상!  ) vehicle (탈것) 이라는 superClass를 생각해봅시다. run() , stop() 등의 기능(메서드)가 있을 수 있겠네요 .

탈것에는 자동차도, 비행기도, 배도 있죠.
잘 알다시피, subClass로 구성할 수 있습니다.

엔진을 돌리기위해 주유하는 기능도 있어야하겠네요. refuel();

여기까진 객체지향의 상속구조만으로 구현하는데 아무 문제가 없습니다.
그런데 -
탈것에 인력거(!!)를 추가해달라는 요청을 받았습니다.

<Problem 1 - 일부 subclass에는 필요없는, 있어서도 안되는 기능의 추가>

가고 (run) , 멈추는 (stop) 기능들은 인력거에 있어도 아무 문제없지만, 주유(refuel)의 기능은 필요가 없습니다.
어떻게 해야할까요 ?

1. superclass에 그냥 추가 되있지만 호출만하지 않으면 되지 않냐 ?

이 주장은 객체지향의 지향점과 조금 다릅니다.
인력거가 refuel을 호출하지 말아야 한다는 것을 알고있어야 한다는건 결국 은닉을 할 수 없다는 얘기가 되니까요 .
다른 개발자가 인력거를 이용할때 refuel을 호출해 버릴 수가 있거든요. 아까운 기름....! 버그의 여지가 있습니다.

2. run()과 stop()은 부모에 그대로 두고 interface를 이용해서 각각의 subclass에서 refuel을 구현하는건 어때 ?

자동차나 비행기나 배나 주유하는건 같은데 , 각각 구현을 따로 해야하는 상황입니다.
이런 코드의 중복성은 탈 것의 종류가 늘어남에따라 유지보수를 기하급수적으로 힘들게 할거에요!

또 객체지향적으로 어떤 방법이 있을까요 ?
흠... 우리가 알고있는 상속만으로는 이 문제를 쉽게 해결하기가 어려워 보입니다.


<Design Pattern - Strategy Pattern>

Problem 1의 요는 이렇습니다.
탈 것이라면 다 같이 있어야하는 기능 (run(),stop()) 은 상속을 통해(다 쓸거니까) 중복성을 제거했으면 좋겠고,
일부 subclass에 없는 기능은 필요한 녀석들만 이용하되, 중복되지 않았으면 좋겠다!

그럼 일단 2. 의 생각과 같이 run()과 stop()을 부모에 그대로 두고 자동차 ~ 인력거까지 탈것의 자식으로 둬봅시다.
문제가 되는 refuel()은 Refuelable 라는 인터페이스를 만들어 거기에 메서드로 붙여봐요 .
여기까진 2.와 같습니다.
단 , refuelable 로 끝내는 것이 아니라 - 주유 가능한 탈것을 위한 클래스를 또 만듭니다.
class RefuelableVehicle implements Refuelable {
        public void refuel(){
                 //기름을 넣자.
       }
} 


라는 클래스를 만들고 Vehicle 이 객체의 인스턴스를 가지고 있다가 위임 - 다른 객체의 인스턴스를 갖고 그 객체의 기능을 호출함 - 을 합니다.
다음과 같이요.
class Vehicle {
protected Refuelable refuable; //위임을 받을 객체
        public void doRefuel(){
                refuable.refuel();
        }
        public void run(){
                //부다다다당~
        }
        .....stop()..... {} ...
}

자동차는 다음과 같겠네요.
class Car extends Vehicle {
        Car(){
               refuelable = new RefuelableVehicle();
        }
}


이제 우리의 말썽쟁이 인력거는 어떻게 하죠 ?
인력거는 refuelable하지 않으니까, RefuelableVehicle()을 이용하면 안될거같아요.
주유가능하지 않은 탈것을 만들어봅시다.
class NoNeedToRefuelableVehicle() implements Refuelable {
        public void refuel(){
                 system.out.println("주유가 필요없어요. 기름 다시 가져가세요." );
        }
}

이제 다 되었습니다.
class vehicleMovedByPersonsPull extends Vehicle {
        vehicleMovedByPersonsPull(){
               refuelable = new NoNeedToRefuelableVehicle();
        }
}

이제 다른 개발자가 주유기능을 이용하기 위해서는 
어떤 탈것인지 신경쓸 필요없이  doRefuel() 을 불러주면됩니다.

주유 가능하지 않은 인력거는 알아서 주유를 하지 않을거거든요 !


이렇게 변할 수 있는 단위로 따로 클래스를 만들어서 위임하는 패턴을 스트래티지 패턴이라고 합니다.

Definition!
스트래티지 패턴(Strategy pattern) 에서는 알고리즘군을 정의하고 각각을 캡슐화하여 교환해서 사용할 수 있도록 만든다.
스트래티지를 활용하면 알고리즘을 사용하는 클라이언트와는 독립적으로 알고리즘을 변경할 수 있다.