내일배움캠프 36일차 TIL : 디자인 패턴 2

김정환·2024년 11월 4일
0

키워드

  • 디자인 패턴 2

이벤트 기반 프로그래밍

  • 발행(Publish)-구독(Subscribe) 패턴이라고 함. (줄여서 Pub-Sub 패턴)

왜 쓸까

기존의 업데이트 방식

  • A 클래스가 B 클래스의 상태나 변수에 변화가 일어났는지 궁금하다면
    A 클래스는 Update 메서드에서 B 클래스의 특정 변수를 계속 확인해야 함.
  • 레거시 Input방식에서 Update에 if문으로 특정 키 입력을 계속 확인하는 것처럼.
  • 변화가 일어나지도 않았는데 계속 접근해서 검사하는 것은 매우 비효율적인 방법임.

이벤트

  • 이런 비효율을 어떤 상황이나 변화가 일어날 때 이벤트를 호출하는 것으로 개선함.
  • A 클래스는 B 클래스의 상태 변화에 대한 이벤트를 구독해두고
    B 클래스에서 변화가 일어나면 이벤트를 호출해주는 것.
  • 효율적인 코드 실행 흐름을 구성할 수 있음.

대응 방식 유형

발행자 수구독자 수대응 방식
11함수 호출
1can be N옵저버 패턴
can be Ncan be N이벤트 버스 패턴

1. 옵저버 패턴

  • 발행자가 이벤트가 발생하면 알려주는 패턴 (이벤트 기반 설계 시 많이 활용)
  • 업데이트 방식에선 구독자들이 발행자에게 지속적으로 물어봤지만,
    옵저버 패턴에선 발행자가 행위가 일어나면 구독자들에게 알려줌.
  • 단, 경우에 따라서 구독 시에 자신과 상관없는 것도 받아야할 수 있음.
// 발행자
public class Publisher : MonoBehaviour
{
    public event Action OnEvent;
    // 생략
}

// 구독자
public class Subscriber : MonoBehaviour
{
	public Publisher pub;
    
    void Start()
    {
    	pub.OnEvent += EventCall;
    }
    
    void EventCall()
    {
    	// ~
    }
}

중요

  • 대체로 위처럼 구현하는데 구독의 경우 메서드를 변수처럼 + 연산으로 추가함.
  • 이때, 메서드만 등록하는 것이 아님.
    • 등록 시, 메서드와 함께 어떤 인스턴스가 등록되었는지 같이 저장됨.
    • 다시말해 이벤트에는 (A 인스턴스의 AA 메서드, B 인스턴스의 BB 메서드)와 같이
      주체와 행동이 같이 저장됨.
    • 동일한 클래스에서 여러 인스턴스가 만들어진 경우, 한 인스턴스만 구독 상태를 유지하고 있음.
      발행자가 이벤트를 발행하면, 구독한 인스턴스 하나만 이 이벤트를 받을 수 있음.
  • 추가적으로 델리게이트는 직렬화할 수 없음.
    • 권장하는 방법은 다시 등록하는 것.
    • 사실 오픈소스를 이용해서 가능하도록 할 수도 있지만 다시 등록하는 것을 권장함.

2. 이벤트 버스

  • 1개 이상의 발행자와 1개 이상의 구독자의 처리가 가능한 패턴.
  • 옵저버 패턴은 1명의 발행자와 n명의 구독자를 상정한 패턴이고
    이벤트 버스는 n명의 발행자와 n명의 구독자를 처리하기 위한 패턴임.

왜 쓸까

  • 이벤트 구현 시, 발행자와 구독자 사이에 직접적인 의존성을 만들지 않고
    이벤트 기능을 만들 수 있음.

적용 사례

  • 퀘스트 기능 구현
  • 대체로 이벤트를 매우 다양한 상황에서 발행해야할 때 이를 쉽게 관리하게 위해 사용.

구현

  • EventBus는 하나 이상의 액션을 관리하기 때문에 대체로 Dictionary를 이용.
  • Publisher가 1명 이상이기 때문에 특정 Publisher에게 구독하지 않고 대리자(이벤트버스)를 통해서 모든 구독을 관리하는 것.
  • 아래 예시의 Publisher와 같이 이벤트 버스를 통해서 이벤트를 발행함.
using System;
using System.Collections.Generic;
using UnityEngine;

public enum QuestEvent
{
    Hunt,
    Explore,
    Build
}

public static class QuestEventBus
{
	// 접근성을 위해서 static 사용
    static Dictionary<QuestEvent, Action> questEventMap = new Dictionary<QuestEvent, Action>();

    // 등록
    public static void Subscribe(QuestEvent questEvent, Action listener)
    {
        if (questEventMap.TryGetValue(questEvent, out Action questAction))
        {
            questAction += listener;
            questEventMap[questEvent] = listener;
        }
        else
        {
            questAction = listener;
            questEventMap.Add(questEvent, questAction);
        }
    }    

    // 해제
    public static void Unsubscribe(QuestEvent questEvent, Action listener)
    {
        if (questEventMap.TryGetValue(questEvent, out Action questAction))
        {
            questAction -= listener;
            if(questAction == null)
            {
                questEventMap.Remove(questEvent);
            }
            else
            {
                questEventMap[questEvent] = listener;
            }

        }
    }

    public static void Publish(QuestEvent questEvent)
    {
        if (questEventMap.TryGetValue(questEvent, out Action questAction))
        {
            questAction?.Invoke();
        }
    }
}

// 사용 예시

public class QuestListener : MonoBehaviour
{
    [SerializeField] QuestEvent questType = QuestEvent.Hunt;
    void OnEnable() 
    {
        QuestEventBus.Subscribe(questType, Test);    
    }

    void OnDisable() 
    {
        QuestEventBus.Unsubscribe(questType, Test);    
    }

    void Test()
    {
        Debug.Log("Listener Test called");
    }
}

public class QuestPublisher : MonoBehaviour
{
    public void OnDead()
    {
        QuestEventBus.Publish(QuestEvent.Hunt);
    }
}

고려사항

  • 이벤트 버스에 대한 의존성이 너무 높아질 가능성이 있음
    • 이럴 경우 단위 테스트 및 관리가 어려워질 수 있음.
    • 누가 보냈는지 알기 어려워질 수 있음.
      • 보통 이를 위해서 enum을 통해 찾기 쉽게 구현함.
      • enum 값을 통해서 발행한 경우를 쉽게 찾을 수 있음.
  • 복잡해질 경우 이벤트 관리가 어려워짐.
    • 이벤트에서 에러가 발생하면 어디서 문제가 생겼는지 알기 어려워짐.

패턴의 유사성

  • 옵저버 vs 이벤트 버스 패턴은 서로 비슷해보일 수 있음.
  • 그래서 이 둘을 묶어 Pub-Sub 패턴이라 일컫는 것.

차이

  • 이벤트 버스는 옵저버와 달리 중간에 버스(대리자)가 의존성을 받아줌.
  • 그러므로 두 클래스간 직접적인 의존성이 없음.

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

profile
사파 개발자

0개의 댓글