본문 바로가기

Design pattern

[JAVA 디자인패턴] Strategy pattern(스트레티지 패턴, 전략패턴)

Strategy pattern(스트레티지 패턴)

같은 문제를 해결하는 여러 알고리즘이 클래스별로 캡슐화되어있다. 특정 알고리즘(전략)을 바꾸려면 해당 클래스를 교체해서, 다른 방식으로 해결할 수 있게 해주는 디자인 패턴이다.

스트레티지 패턴을 이해하기 위해, 로봇들을 만들어보자.

Robot 추상 클래스를 작성해서 이를 상속받은 클래스들이 어떠한 역할을 할지 기술하였다.

 

attack과 move 추상메소드는 자식클래스에서 재정의하도록 하였다. 왜냐하면 어떤 로봇인지에 따라 움직이는 방식, 공격하는 방식이 다르기 때문이다. Atom이 펀치를 날린다면, TaekwonV는 미사일을 발사한다.

 

이와같은 설계방식은 적절해보인다. 다른 로봇을 만들때, 해당 클래스를 상속받아서 재구현하면 된다. OCP를 만족한다. 즉, 특정 기능(클래스)을 추가하는데 기존의 코드가 변경되지 않는다.

 

만약, Sungard 로봇을 만든다고 해보자. Sungard의 이동기능을 수영하는 것으로 하고, 공격기능을 TaekwonV(펀치)와 같게 설정한다. 그러면, Sungard의 공격기능에 TaekwonV의 공격 코드를 그대로 복사 붙여넣기 하면 된다. 이것도 OCP를 만족한다. 하지만, 코드를 복사 붙여넣기 하는 것은 매우 좋지않다 이를, 코드에서 냄새가 난다고 한다. Bad smell 상태이다.(실제 소프트웨어 공학에서 쓰이는 표현)

 

결론적으로, 저 구조를 이용하면 OCP는 지켜지지만 다른 코드를 복사 붙여넣기 하는 상태가 된다. 이는 유지보수를 더욱 어렵게 만든다. 이러한 상황을 해결하기 위한 디자인 패턴으로 strategy pattern이라고 한다.

strategy patten은 특정 기능들을 클래스로 캡슐화한다. 그리고 기능을 바꾸려면 해당 클래스만 바꿔주면 된다. 이 뜻을 클래스 다이어그램을 통해서 알아보자.

Atom이 필요한 Move의 기능과 Punch의 기능을 인터페이스 상속을 통해서 구현해주고, setter를 이용해서 기능을 넣어주면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public abstract class Robot{
 
    private String name;
    private AttackStrategy attackStrategy;
    private MovingStrategy movingStrategy;
    //.. constructor, getter/setter
 
    public void attack(){
        attackStrategy.attack();
    }
 
    public void move(){
        movingStrategy.move();
    }
}
 
public class Atom extends Robot{
    public Atom(String name){
        super(name);
    }
}
 
public interface MovingStrategy(){
    public void move();
}
 
public interface AttackStrategy(){
    public void attack();
}
 
public class FlyingStrategy implements MovingStrategy{
    public void move(){
        System.out.println("Flying");
    }
}
 
public class PunchStrategy implements AttackStrategy{
    public void attack(){
        System.out.println("Attack");
    }
}
 
public class Main{
    public static void main(String[] args){
       Atom atom1 = new Atom("atom1");
        atom1.setMovingStrategy(new FlyingStrategy());
        atom1.setAttackStrategy(new PunchStrategy());
 
        atom1.attack();
        atom1.move();
    }
}
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter

 

 

그런데 왜, 인터페이스 상속을 하는 걸까? 그러기 위해서 원론적이면서 중요한 이야기를 조금 해보자.

클래스의 정의는 무엇인가? 교수님께서 물어보셨는데 대부분의 학생들이 멤버변수와 메소드로 이루어진 것이라고 답한다고 하셨다. 하지만, 교수님의 답은 내가 생각하는 클래스의 정의와 많이 달랐고, 아주 핵심을 찌르는 답이였다. 바로 클래스는 변화의 단위라는 것이다.

 

만약, Atom의 이동방식이 수영하는 방식으로 바뀌고, 미사일로 공격을 한다고 하자. 무엇이 변하는가? 이동방식이 변한다. 그러면 이동방식에 대한 클래스를 구현하면 된다. 변화의 단위를 이동방식으로 본 것이다. 만약, 공격방식도 바뀐다면, 공격방식에 대한 클래스를 새로 구현하면 된다.

 

클래스는 변화의 단위이다. 변화의 단위를 인지하고, 클래스로 모델링한다. 그리고 해당 클래스를 특정 알고리즘으로 구현한다. 만약 해당 알고리즘을 바꾸려면, 변화의 단위인 클래스를 교체하면 된다.

 

이동방식이라는 크고 추상적인 개념을 interface로 선언하였다. 그리고 해당 interface를 implement하였다. 그러면 특정 이동방식이 추가된다면, 큰 개념을 재구현하는 클래스를 생성하면 된다.

 

Atom의 이동방식과 공격방식이 바뀜에따라 추가되는 클래스들을 보자.

 

이동방식이라는 크고 추상적인 개념이 MovingStrategy이다. 해당 인터페이스를 구현하는 클래스 SwimmingStrategy를 생성하였다. 변화하는 단위를 클래스로 보고 해당 기능을 구현하였다.

공격방식이라는 크고 추상적인 개념이 AttackStrategy이다. 해당 인터페이스를 구현하는 클래스 MissileStrategy를 생성하였다. 변화하는 단위를 클래스로 보고 해당 기능을 구현하였다.

 

의존성 역전 원칙을 만족시키기 위해서도 일반화된 개념과 연관관계를 맺는 것이 중요하다. 일반적인 개념(interface, abstract)과 의존관계를 맺고, setter로 구체적인 개념(자식클래스)을 넣어주는게 더 적절하다.

 

strategy 패턴은 패턴이라고 이름을 지을 필요도 없이 너무나도 당연하게 써야하는 방법이다. OCP에서 가장 중요한 설계 방식이다. 

 

 

참고문헌

JAVA 객체지향 디자인 패턴, 정인상