내일배움캠프 34일차 TIL : 디자인 패턴 특강 1

김정환·2024년 10월 31일
0

키워드

  • 디자인 패턴 특강

디자인 패턴 특강 1부의 개념 정리 시간을 가졌다.

디자인 패턴 특강

쓰는 이유

  • 복잡하고 난해한 문제를 패턴화해서 풀 수 있다.(유형화) == 기출문제집
  • 다른 개발자와 소통할 때 간단하게 설명할 수 있다.

유의

  • 문제가 생겼을 때 풀기 위해서 사용하는 것.
  • 디자인 패턴을 위한 개발은 지양해야한다.
  • 만일 썼다면 왜 썼는지가 분명해야한다.

특징

  • 표현에 비유가 많다.
  • 디자인 패턴을 쓴다고 성능이 반드시 좋아지는 것은 아니다.
    하지만, 코드가 더 느슨해지거나 유연해지는 효과를 얻을 수 있다.

1. 싱글톤

쓰는 이유

  • 매우 중요하고 유일하게 존재하는 대상에 쉽게 접근하기 위해서 사용한다.
  • 접근이 잦은 핵심 기능전역적으로 접근하도록 한다.

구현 방법

  1. 유일한 인스턴스 유지한다.

    • 간단한 방법으로 새로운 인스턴스가 생기면 Destroy하는 것이있다.
    • Unity 외적인 방법으로는 클래스의 생성자를 private으로 하여 외부에서 생성하지 못하게할 수 있다.
    • 예시)
    void Awake()
    {
        if (instance != null)
        {
            Destroy(gameObject); //이미 있는 경우 새로운 인스턴스는 파괴
            return;
        }
    
        instance = this;        
    }
  2. 전역적 접근

    • 어디서든 쉽게 접근하기 위해서 인스턴스를 static(정적)으로 선언한다.
    • static 키워드가 붙은 변수나 메서드는 인스턴스 없이도 클래스에서 바로 사용가능하다.
    • 일반적인 변수는 쓰임이 다하면 메모리 자원을 반환하지만
      static 키워드를 사용하면 프로그램이 끝날 때까지 메모리에 상주한다.
    public class ClassExample : MonoBehaviour
    {
        public static ClassExample instance;
        // 생략
    }

이렇게하면 생성된 하나의 인스턴스 메모리에 상주하며 어디서든 접근할 수 있으며
새로운 인스턴스

제너릭 연계

  • C#에서는 일반화 문법인 제너릭(Generic) 기능이 있다.
  • 그리고 싱글톤 패턴은 이 제너릭을 이용해서 확장이 가능하다.
    상세한 구현 예시

쓰는 이유

  • 싱글톤 객체는 구현 방식이 대체로 유사하다.
  • 원래는 싱글톤 기능을 가진
  • 하지만 상속으로 상위 클래스에 접근하는 식으로는 사용하고 싶지 않음.
  • 이때 제너릭을 이용해서 중복을 줄이면서 외부에서 동적으로 호출할 수 있음.

사용 예시

  • 매니저 스크립트 (관리자)
    • 매니저는 유일해야함.
    • 매니저는 참조가 활발한 객체여야함.
  • 많은 객체가 활발하게 유일한 매니저를 통해야할 때 싱글톤으로 구현할 수 있음.

2. 오브젝트 풀

쓰는 이유

  • 게임 오브젝트의 생성과 파괴는 비용이 매우 큰 작업임.
    • GC 호출 유도, 메모리 할당, 해제는 성능에 큰 영향을 줌.
  • 그래서 생성, 파괴가 활발한 오브젝트는 재활용해서 메모리 낭비를 줄이는 것.

고려사항

  • 씬 로드 시에 오브젝트 풀의 모든 오브제트를 전부 다 생성해 두어야 한다.
    • 이럴 경우, 씬 로드 시간이 매우 길어질 수 있으므로 필요한 만큼만 생성할 것.

구현 방법

  • 엄격하게 규정된 방법은 따로 없지만 사용한 오브젝트를 파괴하지 않고 비활성화 시킨다는 것은 필수 사항임.
  • 배열, List Queue와 같은 자료구조를 이용하여 pool을 정의함.

구현 예시

유의

  • 만약 요청이 너무 많아 오브젝트를 줄 수 없는 상황이 온다면?
    이때의 대처 방식은 크게 2가지임. (물론 구현 방법에 따라 더 있을 수 있음.)
    • 대처 1. 새로 만들어준다.
      기존에 설정한 크기를 상회했을 때, 새로 만들어주고 pool에 등록해둘 수 있음.
      • 단, 이럴 경우 무한정 생성하다가 사용하는 목적을 위배할 수 있으니, pool의 최대 크기를 정해주어야 한다.
      • 최대 크기 이상으로 오브젝트가 필요한 경우, 임시 오브젝트를 생성하고 반납시 파괴해줌.
      • 그래서 이 대처법은 최대 크기를 어느정도 정할 것인지 유의해야 함.
    • 대처 2. 사용 중인 것에서 가장 오래된 오브젝트를 강제 반납하고 사용한다.
      • 대처 1과 같이 오브젝트를 새로 만들지 않아 메모리 측면에서 큰 부담은 없겠지만,
        사용중인 오브젝트를 강제로 반납하고 사용하기에 의도치 않은 상황이 발생할 수 있음.
  • 어떤 대처 방법을 취할 것인지는 개발 상황에 따라 선택해야함 .

3. 전략 패턴

쓰는 이유

  • 원본 클래스를 건드리지 않고 다양하게 추가되는 방식을 대응하기 위해서 사용.
  • 상황에 맞게 사용할 로직을 교체해서 사용하는 방법.

    골프에서 공을 친다는 행위는 동일하게 하되, 용도에 맞는 채를 바꿔 사용함으로써
    공을 멀리 보내거나 가까운 목표로 세밀하게 조절해서 치는 것과 같음

전략

  • 로직들의 군집을 정의하고 같은 로직군들은 교체 가능하도록 한다.

구현

  • 인터페이스로 구현하는데, 이때 인터페이스 명칭 뒤에 Strategy(전략)을 붙인다.
  • 그리고 전략 패턴을 사용하는 오브젝트는 인터페이스를 통해서 메서드를 호출한다.
// 전략 인터페이스
public interface IRideStrategy
{
    void Ride();
}

public class Bicycle : IRideStrategy
{
    public void Ride()
    {
        Debug.Log("Ride Bicycle");
    }
}
public class Car : IRideStrategy
{
    public void Ride()
    {
        Debug.Log("Ride Car");
    }
}
public class Helicopter : IRideStrategy
{
    public void Ride()
    {
        Debug.Log("Ride Helicopter");
    }
}

public class Person : MonoBehaviour
{
    IRideStrategy ridableStrategy;

    public void SetRideStrategy(IRideStrategy strategy)
    {
        ridableStrategy = strategy;
    }

    public void Ride()
    {
        ridableStrategy.Ride();
    }
}

4.상태 패턴

쓰는 이유

  • 한 객체에 여러 상태가 있고 상태에 따라 로직이 달라지는 경우에 체계적으로 관리하고 싶음.
  • NPC 캐릭터는 idle, angry, dead와 같은 상태가 있고, 상태에 맞는 행동을 취하도록 함.

사례

  • Unity Animator가 가장 전형적인 예시
  • 위와 같이 정해진 n개의 상태에서 전이(transition)가 일어나며 작동하는 것을
    유한 상태 기계(Finite-State Machine)라고함.

구현

  • StateMachine : 상태의 전환을 관리하는 역할
public class StateMachine
{
    IState curState;

    public void ChangeState(IState newState)
    {
        if(curState != null)
            curState.Exit();
        
        curState = newState;
        curState.Enter();
    }

    // MonoBehaviour의 Update가 아님.
    // 이 클래스 아무것도 상속받지 않고 있기에 별개의 메서드로 봐야 함.
    public void Update()
    {
        if(curState != null)
            curState.Excute();
    }
}
  • IState : 상태에 대한 인터페이스
public interface IState
{
    // 상태들에 들어갈 때, 나올 때, 매 프레임이 중요
    void Enter(); // 상태에 들어갈 때 == OnEnable()
    void Excute(); // 매프레임 실행할 때 == Update()
    void Exit(); // 상태에서 나올 때 == OnDisable()
}
  • State : IState를 구현하며 특정 상태에서 실행될 로직
public class IdleState : IState
{
    public void Enter()
    {
        // Idle 상태 시작 시 호출
    }

    public void Excute()
    {
        // Idle 상태 중 호출
    }

    public void Exit()
    {
		// Idle 상태 종료 시
    }
}

public class WanderState : IState
{
    public void Enter()
    {
		// Wander 상태 시작 시 호출
    }

    public void Excute()
    {
        // Wander 상태 중 호출
    }

    public void Exit()
    {
        // Wander 상태 종료 시
    }
}

HFSM (Hierachial Finite-State Machine)

  • 보다 큰 개념의 상태를 구현하거나
  • 실제 행동을 결정하는 세부 상태를 구분해서 구현하는 것
  • 기존에 State는 그 자체로 하나의 상태를 구현하고 있었다면,
    HFSM인 State가 세부적으로 더 많은 상태로 나눌 필요가 있어 세분화된 것.

차이

  • FSM : Idle, Jump, Walk, Run
  • HFSM : Idle, Jump, Move(Walk, Run)

#내일배움캠프 #스파르타내일배움캠프 #스파르타내일배움캠프TIL

profile
사파 개발자

0개의 댓글