[C#, Unity] 옵저버 패턴 (Observer pattern)

alsry._.112·2024년 6월 6일
0

디자인패턴

목록 보기
3/3
post-thumbnail

옵저버 패턴이 무엇인가

옵저버 패턴(Observer Pattern)은 옵저버(관찰자)들이 관찰하고 있는 대상자의 상태가 변화가 있을 때마다 대상자는 직접 목록의 각 관찰자들에게 통지하고, 관찰자들은 알림을 받아 조치를 취하는 행동 패턴이다.

실생활에서 비유해보자면 유튜브의 구독을 예로 들 수 있겠다.
유튜브 채널은 발행자가 되고 구독자들은 관찰자(Observer)가 된다.
만약 채널에서 영상을 올리면 여러명의 구독자들은 모두 영상이 올라왔다는 알림을 받는데, 이를 패턴 구조로 보자면 구독자들은 해당 채널을 구독함으로써 채널에 어떠한 변화가 생기게 되면 바로 연락을 받아 탐지하는 것이다.

유니티에서의 옵저버

사실 옵저버 패턴이란 말을 들어보지 않아도 개발을 하며 옵저버 패턴을 사용한 경우가 많을 것이다. 나 같은 경우에도 그랬는데, 이는 c#의 Action이나 유니티의 UnityEvent가 대표적인 옵저버 패턴이기 때문이다.

using System;
using UnityEngine;

public class Health : MonoBehaviour
{
    public event Action<float> OnHealthChanged;
    public event Action OnDeath;

    private float currentHealth;
    public float maxHealth { get; private set; } = 100;

    public float HP
    {
        get { return currentHealth; }

        private set
        {
            currentHealth = Mathf.Clamp(value, 0, maxHealth);
        }
    }

    private void Awake()
    {
        currentHealth = maxHealth;
    }

    public void TakeDamage(float amount)
    {
        currentHealth -= amount;

        OnHealthChanged?.Invoke(currentHealth);
        if (currentHealth <= 0)
        {
            OnDeath?.Invoke();
        }
    }

    public void Heal(float amount)
    {
        currentHealth += amount;

        OnHealthChanged?.Invoke(currentHealth);
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class Healthbar : MonoBehaviour
{
    [SerializeField]
    private Slider _healthSlider;
    private Health health;

    private void OnEnable()
    {
        health.OnHealthChanged += UpdateHealthBar;
        health.OnDeath += HandleDeath;
    }

    private void OnDisable()
    {
        health.OnHealthChanged -= UpdateHealthBar;
        health.OnDeath -= HandleDeath;
    }

    private void Start()
    {
        _healthSlider.maxValue = health.maxHealth;
        _healthSlider.value = health.HP;
    }

    private void Awake()
    {
        health = GetComponent<Health>();
    }

    private void UpdateHealthBar(float currentHealth)
    {
        _healthSlider.value = currentHealth;
    }

    private void HandleDeath()
    {
        Debug.Log("죽었습니다.");
    }
}

옵저버 패턴을 이용해서 간단한 hp 기능을 구현하였다.
이 코드에서 Health 클래스는 주체(Subject) 역할을 하며, Healthbar 클래스는 옵저버(Observer) 역할을 한다. Health 클래스는 체력 변경 시 OnHealthChanged 이벤트를 통해 모든 옵저버들에게 알리며, Healthbar 클래스는 이 이벤트를 구독하여 체력 변화를 감지하고 슬라이더를 업데이트한다.

Health
외부에서 Health클래스의 TakeDamage나 Heal을 실행하면 체력이 변경되었음을 알리기 위해 OnHealthChanged 이벤트를 호출한다. 만약 currentHealth가 0 이하가 되면 사망했음을 알리기 위해 OnDeath 이벤트를 호출한다.

Healthbar
health 객체의 OnHealthChanged 이벤트에 UpdateHealthBar 메서드를 구독하고,
health 객체의 OnDeath 이벤트에 HandleDeath 메서드를 구독한다.

Health클래스에서 OnHealthChanged를 실행한다면 UpdateHealthBar 메서드가 호출되고, OnDeath를 실행한다면 HandleDeath 메서드가 호출된다.

옵저버 패턴의 장점

  • 장점
    느슨한 결합:
    옵저버 패턴을 사용하면 주체(Subject)와 옵저버(Observer)가 느슨하게 결합된다. 주체는 자신에게 등록된 옵저버들을 알 필요가 없기 때문에 객체 간의 결합도가 낮아지고 시스템의 유연성이 증가한다.

  • 확장성:
    새로운 옵저버를 쉽게 추가할 수 있다.

  • 재사용성 (Reusability):
    주체와 옵저버를 독립적으로 재사용할 수 있다. 예를 들어, 동일한 주체를 여러 다른 상황에서 재사용할 수 있고, 다양한 옵저버들을 동일한 주체와 함께 사용할 수 있다.

사용 시 주의해야 할 점

  • 복잡성:
    옵저버 패턴을 사용하면 주체와 여러 옵저버 간의 관계를 관리해야 하므로 많은 수의 옵저버를 관리할 경우 시스템의 복잡성이 증가할 수 있다.

  • 예상치 못한 업데이트 순서:
    여러 옵저버가 주체의 상태 변화를 감지할 때, 옵저버들이 통지를 받는 순서는 알 수 없다.

마치며

이처럼 옵저버 패턴은 분명 프로그래밍을 하는데 있어서 일관성을 유지하고 확장성을 높이는 데 유용하지만, 과도하게 사용 시 복잡한 코드 때문에 추후에 리팩토링하기 힘들기 때문에 이러한 장단점을 이해하고 상황에 맞게 적절히 활용해야 한다.

profile
소통해요

0개의 댓글