스트레티지 패턴 (strategy pattern) -전략패턴


- 행위를 클래스로 캡슐화해서 전략을 쉽게 바꿀수 있도록 해주는 패턴

- 해당 패턴을 활용하면 알고리즘을 사용하는 클라이언트와는 독립적으로 알고리즘 변경 가능


< 그림 설명 >

Strategy    

인터페이스나 추상클래스로 외부에서 동일한 방식으로 알고리즘을 호출하는 방법을 명시


ConcreteStrategy

스트레티지 패턴에서 명시한 알고리즘을 실제로 구현한 클래스


Context

스트레티지 패턴을 이용하는 역할

필요에 따라 동적으로 구체적인 전략을 바꿀 수 있도록 Setter 메서드 (집약관계)를 제공한다.







예제 1) 오리게임 예제

최초 Duck 추상 클래스는 공통으로 swim 하거나 quack 기능만가지고 가지고 있다



1. 객체지향 기법을 사용하여 Duck 슈퍼클래스를 만들고, 그 클래스를 확장하여 다른 종류의 오리( ReadHeadDuck, MallardDuck )를 만듬




2. 날수있는 기능을 추가해달라는 요구사항이 생겨서 fly() 메서드를 추가


그런데 날 수 없는 오리가 존재했다. RubberDuck은 장난감 고무오리라 날면 안된다. 

Duck 코드의 한부분만 바꿔서 프로그램 전체에 부작용이 발생했다.


3. 이를 해결하기 위해 RubberDuck 클래스에 fly(), quack() 메서드를 오버라이드 하여 기능변경 시도


당장 급한 문제는 해결됐지만. 

향후 가짜 오리가 더 추가되면 그때마다 맞지않는 quack, fly 메서드들을 오버라이드 해야하는 문제가 여전히 존재


4. 문제의 해결


클래스 상속은 서브클래스들의 행동이 바뀔수 있지만, 하나의 행동을 사용하는 것이 문제가 되고,  인터페이스 역시 코드 재사용을 할수 없는 문제가 있다.


한가지 행동을 바꿀때마다, 그 행동이 정의되어 있는모든 서브클래스들의 코드를 일일이 찾아서 고쳐야하고, 그과정에서 새로운 버그가 생길수도 있다.


애플리케이션에서 달라지는 부분을 찾아내고, 달라지지 않는 부분으로부터 분리시킨다. "


달라지는 부분을 찾아서 "캡슐화"


fly() 와 quack() 메서드를 Duck 클래스로부터 분리시키기 위해 FlyBehavior , QuackBehavior 인터페이스를 새로 만들고, 해당 인터페이스를 구현하는 새로운 클래스의 집합을 만든다.


" 상속보다는 구성을 활용하고, 구현이 아닌 인터페이스에 맞춰서 프로그래밍 한다. "


Duck 클래스에 인터페이스형식의 인스턴스를 추가하고 performFly, performQuack 메서드를 추가하여 FlyBehavior, QuackBehavior 인터페이스의 객체에 그 행동을 위임


setFlyBehavior, setQuackBehavior Setter 메서드를 추가하여 외부에서 행동을 임의대로 바꿔줄 수 있도록 처리


5. 완성

1. 행위를 담당하는 인터페이스 ( FlyBehavior, QuackBehavior )

2. 이를 구현한 클래스들 ( FlyNoWay, MuteQuack  등등 )

3. 인터페이스 인스턴스 변수를 가지고 있는 Duck 클래스

4. 클래스에는 인터페이스의 객체에 행동을 위임하는 메서드와 외부에서 행동을 임의대로 바꿔줄 수있는 Setter 메서드(집약관계) 존재 

- 집약관계

a. 참조값을 인자로 받아 필드를 세팅하는 경우

b. 전체객체와 부분 객체의 라이프타임은 독립적이다.

c. 즉, 전체 객체가 메모리에서 사라져도 부분 객체는 사라지지 않는다. 

5. Duck 클래스를 상속받아 새롭게 정의되는 클래스들 ( RubberDuck, ReadHeadDuck, MallardDuck )


1
2
3
4
// Fly행동만 담당하는 인터페이스
public interface FlyBehavior {
    public void fly();
}
cs


1
2
3
4
// Quack 행동만 담당하는 인터페이스
public interface QuackBehavior {
    public void quack();
}
cs

1
2
3
4
5
6
7
// FlyBehavior 인터페이스를 구현한 클래스 중 한개
public class FlyNoWay implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("no Fly!");
    }
}
cs

1
2
3
4
5
6
7
// QuackBehavior 인터페이스를 구현한 클래스 중 
public class MuteQuack implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println("......mute..");
    }
}
cs




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
// 인터페이스 인스턴스 변수로 둔 추상 클래스
public abstract class Duck {
    
    // 인터페이스 인스턴스 변수 추가
    FlyBehavior flyBehavior;
    QuackBehavior quackBehavior;
    
    // 모든 오리들의 공통적인 기능
    public void swim() {
        System.out.println("swim");
    }
 
    // 구현 클래스들이 사용해야할 메서드
    public abstract void display();
    
    // 인터페이스 참조하는 객체에 행동을 위임
    public void performFly() {
        flyBehavior.fly();
    }
    public void performQuack() {
        quackBehavior.quack();
    }
    
    // 외부에서 행동을 임의대로 바꿔줄수 있도록 setter 메서드 필요
    public void setFlyBehavior(FlyBehavior flyBehavior) {
        this.flyBehavior = flyBehavior;
    }
    public void setQuackBehavior(QuackBehavior quackBehavior) {
        this.quackBehavior = quackBehavior;
    }    
}




1
2
3
4
5
6
7
8
9
10
11
12
13
// Duck 클래스를 상속받아 RubberDuck을 구현한 클래스 중 1개 
public class RubberDuck extends Duck{
 
    public RubberDuck() {
        flyBehavior = new FlyNoWay();
        quackBehavior =new MuteQuack(); 
    }
 
    @Override
    public void display() {
        System.out.println("rubberDuck");
    }
}



1
2
3
4
5
6
7
8
9
10
11
12
// RubberDuck 클래스를 생성하여 사용하는 클래스
public class RubberDuckSimulator {
    public static void main(String[] args) {
        Duck rubberDuck = new RubberDuck();
        rubberDuck.performQuack();        // ......mute..
        rubberDuck.performFly();        // no Fly!
 
        // 날수 있게 수정
        rubberDuck.setFlyBehavior(new FlyWithWings());
        rubberDuck.performFly();    // fly! with Wings!
    }
}
cs









ex) 로봇만들기


1. 걷고 펀치 킥 공격만 하는 TaekwonV와 날면서 미사일공격을 하는 Atom



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Robot 추상 클래스
public abstract class Robot {
     private String name;
    
    public Robot(String name) {
        this.name = name;
    }
    
    public String getName() {
        return name;
    }
    
    public abstract void attack();
    public abstract void move();
}
cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 걸으면서 펀치,킥 공격하는 TaewonV
public class TaekwonV extends Robot{
    public TaekwonV(String name) {
        super(name);
    }
 
    @Override
    public void attack() {
        System.out.println("punch! kick!");
    }
 
    @Override
    public void move() {
        System.out.println("walk move~");
    }
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 날면서 미사일쏘는 Atom
public class Atom extends Robot{
    public Atom(String name) {
        super(name);
    }
 
    @Override
    public void attack() {
        System.out.println("Missile!!");
    }
 
    @Override
    public void move() {
        System.out.println("fly move~");
    }
}




2. Atom을 못날게하고 걷게만 하고 싶다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 이제 못나는 Atom
public class Atom extends Robot{
 
    public Atom(String name) {
        super(name);
    }
 
    @Override
    public void attack() {
        System.out.println("Missile!!");
    }
 
    @Override
    public void move() {
        System.out.println("walk move~");    //걷게만 수정
    }    
}


새로운 기능을 위해 기존 코드내용을 수정해야 하므로 OCP 위배

TaekwonV의 move() 메서드와 중복

걷는 방식에 문제가 생기면 모든 중복코드를 수정해야 하는 이슈발생



3. 새로운 로봇(Sungard)를 만들어 펀치,킥 공격, 걷는 이동방법을 추가/수정 해야 하는 경우


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 펀치,킥 공격에 걷는 Sungard 로봇추가
public class Sungard extends Robot{
    public Sungard(String name) {
        super(name);
    }
 
    @Override
    public void attack() {
        System.out.println("punch! kick!");
    }
 
    @Override
    public void move() {
        System.out.println("walk move~");
    }    
}


Sungard와 TaekwonV의 Attack(), move() 메서드 내용이 중복

마찬가지로 행동에 문제가 생기면 모든 중복코드를 수정해야 함



- 일단 여기까지 클래스 다이어그램



4. 문제의 해결

- 무엇이 변화되었는지 찾은 후에 이를 캡슐화 (이동방식과 공격방식)

- 이를 캡슐화하기 위해 

외부에서 구체적인 이동방식과 공격방식을 담을 구체적인 클래스들을 은닉화 해야함




1. 행위를 담당하는 인터페이스 (AttackStrategy, MovingStrategy)

2. 그를 구현하는 클래스들 (MissileStrategy, FlyingStrategy 등)

3. 인터페이스 인스턴스 변수와 그를 사용하는 Robot 클래스



최종 소스



1
2
3
4
// Attack 인터페이스 
public interface AttackStrategy {
    public void attack();
}


1
2
3
4
5
6
7
8
// Attack 인터페이스를 구현한 클래스
public class MissileStrategy implements AttackStrategy {
    @Override
    public void attack() {
        System.out.println("Missile!!");
    }
}
 



1
2
3
4
// Moving 인터페이스
public interface MovingStrategy {
    public void move();
}


1
2
3
4
5
6
7
// Moving 인터페이스를 구현한 클래스
public class FlyingStrategy implements MovingStrategy {
    @Override
    public void move() {
        System.out.println("fly move~");
    }
}


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
// 추상 클래스
public abstract class Robot {
    private String name;
    AttackStrategy attackStrategy;
    MovingStrategy movingStrategy;
    
    public Robot(String name) {
        this.name = name;
    }
    
    public String getName() {
        return name;
    }
 
    public void attack() {
        attackStrategy.attack();
    }
    public void move() {
        movingStrategy.move();
    }
    
    public void setAttackStrategy(AttackStrategy attackStrategy) {
        this.attackStrategy = attackStrategy;
    }
    public void setMovingStrategy(MovingStrategy movingStrategy) {
        this.movingStrategy = movingStrategy;
    }
}



1
2
3
4
5
public class TaekwonV extends Robot{
    public TaekwonV(String name) {
        super(name);
    }
}



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 사용 클래스
public class Client {
    public static void main(String[] args) {
        Robot taekwonV     = new TaekwonV("TaekwonV");
        Robot atom         = new TaekwonV("Atom");
        
        // 전략 변경 방법
        taekwonV.setMovingStrategy(new WarkingStrategy());
        taekwonV.setAttackStrategy(new PunchStrategy());
        atom.setMovingStrategy(new FlyingStrategy());
        atom.setAttackStrategy(new MissileStrategy());
            
        System.out.println(taekwonV.getName());    // TaekwonV
        taekwonV.move();                        // walk move~
        taekwonV.attack();                        // punch! kick!
        System.out.println(atom.getName());        // Atom
        atom.move();                            // fly move~
        atom.attack();                            // Missile!!
    }
}



+ Recent posts