Posts Strategy Pattern(전략 패턴)
Post
Cancel

Strategy Pattern(전략 패턴)

Strategy Pattern(전략 패턴)


설명

  • 행동 패턴(Behaviour Pattern)의 일종

  • 알고리즘을 클래스화하여, 실행 중에 알고리즘(전략)을 통째로 교체하며 사용한다. (핵심)

  • if-else 또는 switch-case 구문을 통해 어떤 기준에 따라 분기로 작성하던 기능을 각각 클래스화시킨다.

  • 베이스 클래스를 각각 상속받는 전략 클래스들과 이를 교체하며 사용하는 클래스로 이루어진다.

  • 각 전략 클래스는 베이스 클래스를 통해 통일된 행동을 기반으로 구현해야 한다. (중요)

  • 필요에 따라 각 전략 클래스를 싱글톤화하여 재사용할 수 있다.

  • 전략 클래스 내부에서 컨텍스트의 상태 전이 구문을 추가하면 상태 패턴(State Pattern)이 된다.


클래스 다이어그램

image


장점

  • 컨텍스트의 변경 없이 기존의 전략을 수정하거나 새로운 전략을 추가할 수 있다.
    (개방 폐쇄 원칙(OCP) 실현)

  • 알고리즘 추가에 따른 컨텍스트 클래스의 규모 증가를 방지할 수 있다.

  • 동일한 전략을 사용하는 다른 컨텍스트에 재사용할 수 있다.


단점

  • 관리해야 하는 전략 클래스와 객체의 수가 늘어난다.

  • 컨텍스트와 전략 클래스 간의 추가적인 통신 오버헤드가 발생할 수 있다.


활용(유니티)

  • 캐릭터의 공격을 구현할 때, 공격 알고리즘을 클래스화

  • 캐릭터가 처한 환경 조건에 따른 이동 알고리즘을 클래스화

  • 캐릭터가 사용할 스킬을 클래스화


예제(유니티)


  • 캐릭터와 적의 거리에 따른 캐릭터의 공격을 구현한다.

  • 공격 가능 거리 이내에서 적과 너무 가까우면 잡기 공격, 적당히 가까우면 펀치 공격, 멀리 있으면 돌진 공격을 수행한다.


[1] 기존 코드

image

Source Code
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
53
54
55
56
57
58
59
60
61
62
63
/// <summary> 플레이어 캐릭터(컨텍스트 역할) </summary>
public class PlayerCharacter : MonoBehaviour
{
    private const float GrapplingDistance = 1f;
    private const float PunchDistance = 3f;
    private const float DashDistance = 10f;

    // 현재 타겟으로 설정된 적
    private Enemy targetEnemy;

    private void Update()
    {
        if (targetEnemy == null)
        {
            FindEnemy();
        }
        else
        {
            // 적으로부터의 거리 계산
            float distanceFromEnemy = GetDistanceFromEnemy();

            // 거리에 따른 공격 전략 선택
            if (distanceFromEnemy <= GrapplingDistance)
            {
                GrapplingAttack();
            }
            else if (distanceFromEnemy <= PunchDistance)
            {
                PunchAttack();
            }
            else if (distanceFromEnemy <= DashDistance)
            {
                DashAttack();
            }
        }
    }

    public void FindEnemy()
    {
        // 적을 찾아 targetEnemy 필드에 초기화
    }

    // 적으로부터의 거리 계산
    public float GetDistanceFromEnemy()
    {
        return Vector3.Distance(targetEnemy.transform.position, transform.position);
    }

    public void GrapplingAttack()
    {
        Debug.Log("잡기 공격");
    }

    public void PunchAttack()
    {
        Debug.Log("펀치 공격");
    }

    public void DashAttack()
    {
        Debug.Log("돌진 공격");
    }
}
1
2
/// <summary> 적 </summary>
public class Enemy : MonoBehaviour { }


[2] 전략 패턴 적용

  • 싱글톤 패턴, 널 오브젝트 패턴도 함께 적용

image

Source Code
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
53
54
55
56
57
58
/// <summary> 플레이어 캐릭터(컨텍스트 역할) </summary>
public class PlayerCharacter : MonoBehaviour
{
    private const float GrapplingDistance = 1f;
    private const float PunchDistance = 3f;
    private const float DashDistance = 10f;

    // 현재 타겟으로 설정된 적
    private Enemy targetEnemy;

    // 현재 공격 전략
    private IAttackStrategy attackStrategy;

    private void Update()
    {
        if (targetEnemy == null)
        {
            FindEnemy();
        }
        else
        {
            // 적으로부터의 거리 계산
            float distanceFromEnemy = GetDistanceFromEnemy();

            // 거리에 따른 공격 전략 선택
            if (distanceFromEnemy <= GrapplingDistance)
            {
                attackStrategy = GrapplingAttack.instance;
            }
            else if (distanceFromEnemy <= PunchDistance)
            {
                attackStrategy = PunchAttack.instance;
            }
            else if (distanceFromEnemy <= DashDistance)
            {
                attackStrategy = DashAttack.instance;
            }
            else
            {
                attackStrategy = StopAttack.instance;
            }

            // 현재 전략을 통해 적 공격
            attackStrategy.Attack(targetEnemy);
        }
    }

    public void FindEnemy()
    {
        // 적을 찾아 targetEnemy 필드에 초기화
    }

    // 적으로부터의 거리 계산
    public float GetDistanceFromEnemy()
    {
        return Vector3.Distance(targetEnemy.transform.position, transform.position);
    }
}
1
2
/// <summary> 적 </summary>
public class Enemy : MonoBehaviour { }
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
/// <summary> 공격 전략 인터페이스 </summary>
public interface IAttackStrategy
{
    void Attack(Enemy target);
}

/// <summary> 공격 전략 베이스 클래스 </summary>
public abstract class AttackStrategy<T> : IAttackStrategy where T :  AttackStrategy<T>, new()
{
    // 각 하위 클래스마다 생성될 싱글톤 인스턴스
    public static readonly T instance = new T();
    public abstract void Attack(Enemy target);
}

/// <summary> 초근접 - 잡기 공격 </summary>
public class GrapplingAttack : AttackStrategy<GrapplingAttack>
{
    public override void Attack(Enemy target)
    {
        Debug.Log("잡기 공격");
    }
}

/// <summary> 근접 - 펀치 공격 </summary>
public class PunchAttack : AttackStrategy<PunchAttack>
{
    public override void Attack(Enemy target)
    {
        Debug.Log("펀치 공격");
    }
}

/// <summary> 비근접 - 돌진 공격 </summary>
public class DashAttack : AttackStrategy<DashAttack>
{
    public override void Attack(Enemy target)
    {
        Debug.Log("돌진 공격");
    }
}

/// <summary> 공격하지 않음 - Null 패턴의 활용 </summary>
public class StopAttack : AttackStrategy<StopAttack>
{
    public override void Attack(Enemy target)
    {
        Debug.Log("공격 중지");
    }
}


References


This post is licensed under CC BY 4.0 by the author.

CIL 코드 뜯어보기

Texture Painter(텍스쳐에 그림 그리기)