C# 프로그래밍/기초 문법

[c#] 디자인 패턴 - 옵저버 패턴

내공부방 2024. 7. 14. 23:32
반응형

 

옵저버 패턴이란?

- 한 객체가 주체 역할을 하고 다른 객체가 관찰자 역할을 맡는 객체 간의 일대다 관계를 설정하는 것이 핵심!!

- 주체 역할을 맡은 객체는 내부에서 변경되었을 때 관찰자에게 알리는 책임을 진다.

 

사용하는 이유?

- 상태를 자주 변경하고 변경 사항을 대응해야 하는 종속성이 많은 컴포넌트가 있다면 해당 패턴을 사용하는 것을 추천!

 

예제

 

1. 연결/해제/알림 

 public abstract class Subject : MonoBehaviour
{
	...
    protected void Attach(Observer observer)
    {
        _observers.Add(observer);
    }

    protected void Detach(Observer observer)
    {
        _observers.Remove(observer);
    }

    protected void NotifyObservers()
    {
        foreach (Observer observer in _observers)
        {
            observer.Notify(this);
        }
    }
}

 

* Attach - 알림받을 관찰자 목록에 객체를 추가

* Detach - 관찰자 목록에서 관찰자를 삭제

* NotifyObsercers - 주체의 관찰자 목록에 추가된 모든 객체에 알림을 보냄 

 

위와 같이 코드를 구성하여 관찰자 역할을 맡은 객체는 Notify()라는 메서드를 구현하여 상태가 변경되었을 때 알림을 진행한다.

 

2. 옵저버 추상 클래스 (알림) 

public abstract class Observer : MonoBehaviour
{
    public abstract void Notify(Subject subject);
}

옵저버가 되고자 하는 클래스는 Observer 클래스를 상속받아 추상 메서드 구현을 진행

 

3. 연결과 해제 방법 

public class BikeController : Subject, IBikeElement, IDamageable, IDestructible
{
    ...

    private GameObject _hud;
    private HUDController _hudController;


    void OnEnable()
    {
        if (_hudController) Attach(_hudController);

    }

    void OnDisable()
    {
        if (_hudController) Detach(_hudController);
    }
}

 

옵저버 패턴은 활성화/비활성화에 따라 연결과 해제를 해주는 것이 중요하다! 

 

4. 관찰자에게 상태 업데이트 (알림)

public class BikeController : Subject
{
	...
    private bool _isEngineOn;
    private HUDController _hudController;

    void Awake()
    {
        _hudController = 
            gameObject.AddComponent<HUDController>();
            
        ...
    }

    private void Start()
    {
        StartEngine();
    }

    private void StartEngine()
    {
        _isEngineOn = true;

        NotifyObservers();
    }

    public void ToggleTurbo()
    {
        if (_isEngineOn) 
            IsTurboOn = !IsTurboOn;

        NotifyObservers();
    }

    public void TakeDamage(float amount)
    {
        NotifyObservers();

        if (health < 0)
            Destroy(gameObject);
    }
}

 

Bike의 상태를 관찰자에게 알린다. 관찰자가 알림을 받게 되면 독립적으로 행동 방식을 정할 수 있다

 

5. 관찰자 (주체가 신호를 보내면 행동하는 부분)

public class HUDController : Observer 
{
    ...
    
    public override void Notify(Subject subject) {
        if (!_bikeController)
            _bikeController = 
                subject.GetComponent<BikeController>();

        if (_bikeController) {
            _isTurboOn = 
                _bikeController.IsTurboOn;

            _currentHealth = 
                _bikeController.CurrentHealth;
        }
    }
}

 

속성에 접근하여 인터페이스에 표시할 속성들을 선택할 수 있다! 변경된 사항을 듣고 어떻게 반응할지 선택하는 것은

관찰자의 재량이다!

 

장단점

- 장점 :

  • 역동성 : 주체에 필요한 만큼의 객체를 관찰자로 추가할 수 있고, 런타임에 동적으로 제거할 수도 있다.
  • 일대다 : 옵저버 패턴의 주요 이점인 일대다 관계로 객체  간 이벤트 처리 시스템의 구현 문제를 편리하게 해결할 수 있다.

- 단점 : 

  • 무질서 : 옵저버 패턴은 관찰자가 알림받는 순서를 보장하지 않는다. 즉 둘 이상의 옵저버 객체가 종속성을 공유하고 특정 순서에 맞춰 동작해야 하는 것이라면 해당 패턴은 적당하지 않다!
  • 누수 : 주체는 관찰자에 대한 강한 참조를 가져 메모리 누수를 일으킬 수 있다 . 그렇기 때문에 제대로 분리 및 삭제를 해주어야 한다!
반응형