본문 바로가기

JAVA/디자인 패턴

자바/Java 디자인 패턴 1.스트래티지 패턴(Strategy Pattern)

자바/Java 디자인 패턴 1.스트래티지 패턴(Strategy Pattern)




 

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



스트래티지 패턴의 기본정의 입니다. 괴물군도 아니고 알고리즘군이라니 허억.. ㅎㅎ 싱거운 드립이였네요. 아무튼 말이 너무 어렵습니다. 하지만 정작 원리는 간단합니다. 자 그럼 본격적으로 스트래티지 패턴에 대해 알아 볼까요?


자 이제부터 우리는 오리제국을 만들어야 합니다.!! 그러기 위해서는 오리의 기본적인 모양에 해당하는 Duck 이라는 클래스를 만들어 보겠습니다. Duck 클래스는 오리의 기본적인 정보를 담고 있습니다.


자 우리는 Duck 클래스를 상속받아 미니오리, 청둥오리, 노랑부리오리 를 만들어야 합니다.


그림출처 : 네이버 백과사전


그럼 기본 오리틀 Duck 클래스의 구조를 살펴 보겠습니다. 

public abstract class Duck {
	
	public Duck(){
	}
	
    // 오리마다 모양이 다르기 때문에, abstract 메서드로 강제로 차일드 클래스에서 작업 해준다.
	public abstract void display();
	
	public void fly(){
		System.out.println("날고있어요");
	}
	
	public void quack(){
		System.out.println("꽥꽥꽥꽥");
	}
}


Duck 클래스는 위의 구조처럼  구성 되어있는데요. 오리의 기본적인 속성들이 포함되어 있습니다. 그리하여 Duck 클래스를 상속받은  미니리, 청둥오리, 노랑부리오리들은 잘 동작을 하게 되었는데요. 갑자기 고객의 요청으로 모형오리를 추가해 달라는 요청이 들어왔습니다.


자 그럼 어떻게 수정해야 할까요? 모형 오리라... 모형오리는 날지도 못하고, 꽥꽥꽥꽥 짖지도 못하는데 말이죠. 아~~ 참 난감합니다. 그래도 해결책을 찾아보겠습니다.


"fly()와 quack() 메서드를 오버라이드해서 빈 메서드로 놔두게 되면, 모형오리를 만들 수 있을거 같은데..." 맞습니다. 하지만 고객이 모형오리말고 우리나라에 존재하는 140여종의 오리를 모두 추가해 달라고하면, 어떻게 해야 할까요? 필요없는 메서드를 해당 속성이 없는 오리에 전부 추가해줘야 합니다. 그 속성을 다시 빼달라고 하면 --;;; 상상하기도 싫겠죠? 상당히 비효율적인 작업입니다.


"그럼!! Flyable, Quackable 인터페이스를 추가해서 오리마다 implements 해주면 되잖아요!" 네 맞습니다. 하지만 이것 역시 위의 문제점과 마찬가지로 해당 속성에 맞는 오리마다 implements 를 해줘야 하는 번거로움을 가지고 있습니다. 좀더 깔끔한 방법을 찾아야 합니다.


이러한 문제점을 겪고 있을 때, 사용하는 방법이 스트래티지 패턴 입니다. 처음에 설명에 나왔듯 "알고리즘군을 정의하고 각각을 캡슐화하여 교환해서 사용할 수 있도록 만든다." 는 부분이 있었는데요. 쉽게 말해 오리마다 다를 수 있는 부분을 캡슐화하여 교환해서 사용하라는 말로 해석 하시면 됩니다.


자 그럼 fly()와 quack()를 캡슐화 해서 교환해서 사용하는 방법에 대해서 알아 보겠습니다. 

아래는 스트래티지 패턴을 적용한 Duck 클래스 입니다.


public abstract class Duck {
	
	IFlyBehavior iFlyBehavior;
	IQuackBehavior iQuackBehavior;
	
	public Duck(){
	}
	
	public abstract void display();
	
	public void performFly(){
		iFlyBehavior.fly();
	}
	
	public void performQuack(){
		iQuackBehavior.quack();
	}
	
	// 나는 행동 변화
	public void setFlyBehavior(IFlyBehavior fb){
		iFlyBehavior = fb;
	}
	
	// 소리 변화
	public void setQuackBehavior(IQuackBehavior fb){
		iQuackBehavior = fb;
	}
	
}


fly()와 quack() 속성을 각각 IFlyBehavior, IQuackBehavior 에 정의 하여 Duck 클래스의 인스턴스 변수로 선언 하였습니다. 그리고  performFly()와 performQuack() 메서드를 추가해서 각각 인스턴스 변수의 fly(), quack() 메서드를 호출하는 형태로 변경 하였습니다.


IFlyBehavior 인터페이스 구조 입니다.

public interface IFlyBehavior {
	void fly();
}


IQuackBehavior 인터페이스 구조 입니다.
public interface IQuackBehavior {
	void quack();
}


그리고 IFlyBehavior 인터페이스를 구현한 FlyWithWings 클래스를 만들어 줍니다.
public class FlyWithWings implements IFlyBehavior{

	@Override
	public void fly() {
		System.out.println("날고있어요");
	}

}

역시 IQuackBehavior 인터페이스를 구현한 Quack 클래스를 만들어 줍니다.
public class Quack implements IQuackBehavior{

	@Override
	public void quack() {
		System.out.println("꽥꽥꽥꽥");
	}

}


이렇게 하면 기본적으로 fly()와 quack() 속성을 가진 오리들은 FlyWithWings과 Quack 클래스를 이용해서 별다른 수정 작업 없이 오리 차일드 클래스를 만들 수 있습니다. 왜냐하면 Duck 클래스의 performFly()를 호출하게 되면, 해당 변수에 참조 되어 있는 객체의 fly() 메서드를 호출해 주기 때문입니다.


오리가 기본적인 "꽥꽥꽥꽥" 소리를 낸다면, iFlyBehavior 변수에 Quack 클래스를 참조 시켜주면 Quack 클래스의 fly() 메서드가 호출 되는 구조 입니다. 이런 구조가 가능한 이유는 조상의 변수로 차일드의 인스턴스를 참조할 수 있는 개념인 다형성 때문에 가능한 일인데요.


여기서는 깊이 들어가지는 않고 조상의 참조변수로 차일드의 인스턴스는 무조건 참조 할 수 있다고 생각하시면 됩니다. 예들들어 이런 형식입니다. IFlyBehavior fb = new FlyWithWings(); 이런식으로 조상의 참조변수에 FlyWithWings 클래스의 인스턴스를 참조해 줍니다. 그렇게 되면 IFlyBehavior의 fly() 메서드를 호출시 FlyWithWings 클래스의 fly() 메서드를 호출하게 되는 개념 입니다.


인터페이스 역시 클래스라고 생각하시고 구현된 클래스를 자손이라고 생각하시면 됩니다.


그렇다면 실제 Duck 클래스를 가지고 미니오리를 구현한 클래스를 만들어 보겠습니다. 아래는 미니오리에 대한 예제인데요. 미니오리는 별뜻없이 작은 오리라는 뜻입니다.ㅎ 그렇기 때문에 미니오리는 하늘을 나는 행동과 "꽥꽥꽥꽥" 소리를 내는 행동을 하게되는 아주 반듯한 오리입니다. 예제를 보시면,


public class MiniDuck extends Duck {
	
	public MiniDuck(){
		iFlyBehavior = new FlyWithWings();
		iQuackBehavior = new Quack();
	}
	
	@Override
	public void display() {
		System.out.println("작은오리");
	}

}

보시는 것 처럼, IFlyBehavior fb = new FlyWithWings(); 형식으로 인스턴스 변수를 사용해 하늘을 나는 행동을 셋팅해 주었고, 마찬가지로 IQuackBehavior qb = new Quack(); 기본적인 "꽥꽥꽥꽥" 소리를 셋팅해 주었습니다. 만약에 이러한 기본적인 속성이 동일한 오리들은 모두 이런 구조를 가져가게 되면 됩니다.


그림 출처 : http://blog.naver.com/gomdorrine


하지만!! 문제의 모형오리는 어떻게 해야 되는 것일까요!! 모형오리는 하늘을 나는 행동을 하지 못하고, 몸뚱이를 누르면 "꾸에에에에에~~엑" 이라는 소리를 내고 있습니다. 기존오리들과 다른 소리를 내고 있는 상황입니다. 이럴 경우에는 어떻게 해야 할까요?


바로 IFlyBehavior 를 구현한 NoFly 클래스를 만들어 주면 됩니다. 그리고 모형 오리에서는 이런식으로 iFlyBehavior 참조변수에 NoFly 인스턴스를 참조 시켜 주면 됩니다. 그렇게 되면!! NoFly 클래스의 fly() 메서드가 호출되겠죠? 그러면 날지 못하는 오리에 대한 행동을 쉽게 추가 구현 할 수 있습니다. 이런식으로 말이죠.!


public class NoFly implements IFlyBehavior{

	@Override
	public void fly() {
		System.out.println("날지못해요");
	}

}


이렇게 된다면, 고객들이 다른 오리를 추가해 달라는 요청이 들어와도, 기존 속성과 다른 오리들만 골라내서 인터페이스를 구현한 클래스를 만들어 주면 되는 형태로 바뀌게 됩니다. 물론 기존 속성과 같다면 그대로 인스턴스를 만들어서 사용하시면 되구요.


실제 완성된 모형오리(ModelDuck) 클래스 입니다.

public class ModelDuck extends Duck{

	public ModelDuck(){
		iFlyBehavior = new NoFly(); // "날지못해요"
		iQuackBehavior = new QQuack(); // "꾸에에에에에~~엑"
	}
	
	@Override
	public void display() {
		System.out.println("모형오리");
	}
	
}


또한 오리에 swim() 기능을 추가해 달라는 요청이 와도, ISwimBehaivor 인스턴스를 추가한 후, Duck 클래스의 속성 즉 인스턴스 변수로 선언한 후,iSwimBehaivor sb = new Swim() 형태로 기본속성을 지정 하 실 수 있습니다. 물론 Swim()클래스는 ISwimBehaivor 를 구현한 클래스 여야 하겠죠? 만약 기존 오리 수영방법과 다른 오리는 클래스를 추가 구현해줘야 하는 부분이구요.


결국 이 패턴의 핵심은 "A는 B다" 라는 상속 개념에서 벗어난 "A는 B의 속성이다" 라는 개념을 전면적으로 내세우는 패턴이 되겠습니다. "A는 B의 속성이다" 에서 A는 바로 위에서 정의한 IFlyBehavior, IQuackBehavior, ISwimBehaivor 이런 인터페이스들이 되겠고, B는 Duck 클래스 라고 보시면 됩니다.


실제 완성된 오리들을 테스트 해보겠습니다.

public class TestChildDuck {

	public static void main(String[] args) {
		// 작은오리
		Duck miniDuck = new MiniDuck();
		miniDuck.display();
		miniDuck.performFly();
		miniDuck.performQuack();
		
		System.out.println();
		
		// 모형오리
		Duck modelDuck = new ModelDuck();
		modelDuck.display();
		modelDuck.performFly();
		modelDuck.performQuack();
		// 모형오리가 날수 있게 수정 할 수 있다.
		modelDuck.setFlyBehavior(new FlyWithWings());
		modelDuck.performFly();
		
	}

}


테스트 결과 입니다.
작은오리
날고있어요
꽥꽥꽥꽥

모형오리
날지못해요
꾸에에에에에~~엑
날고있어요



마지막으로 전체적인 Flow 는 이렇습니다.



음 어떠신가요? 이렇게 해서 스트래티지 패턴에 대해 알아보았는데요.! ㅎㅎ 감이 좀 오시는 부분이 있나요? 저는 포스팅을 하고도 실전에 어떻게 적용해야 하나? 라는 생각이 드는데요 ㅎㅎ 나중에 쓸 일이 있을 때 꼭 스트래티지 패턴을 써봤으면 하네요.ㅎㅎ


아무튼 긴 포스트 끝까지 정독해 주셔서 감사하구요. 다음번에 옵저버 패턴에 대해 알아보는 시간을 갖도록 하겠습니다. 감사합니다.



파일첨부 : 


TestStrategyPattern.zip






출처 : 해드 퍼스트 디자인 패턴