옵저버 패턴이란?
대상의 특정 행위가 일어나면 관찰자가 구독 중인 객체에게 브로드캐스팅(방송)하여 알려주는 패턴입니다.
특징
- 어떠한 한 행위에 대한 보고를 객체마다 일일이 참조해주지 않고 관찰자에게 구독하고 있으면 된다.
- "Delegate event chain + 단일 인스턴스(Singleton Pattern)" 형태이다.
예제
NotificationCenter.cs (관찰자)
public static class NotificationCenter
{
public delegate void Subscribe(string text);
private static event Subscribe _subscribes = null;
/// <summary>
/// 구독 리스트
/// </summary>
public static event Subscribe Subscribes
{
add
{
_subscribes -= value;
_subscribes += value;
}
remove
{
_subscribes -= value;
}
}
/// <summary>
/// 알림
/// </summary>
/// <param name="title"></param>
/// <param name="text"></param>
public static void Broadcast(string text)
{
_subscribes?.Invoke(text);
}
}
Character.cs
public abstract class Character
{
public float Hp { get; protected set; } = 100f;
public float Defense { get; protected set; } = 0f;
public float Power { get; protected set; } = 0f;
public void Attack(Character target)
{
target.DamageProcess(this.Power);
}
public void DamageProcess(float power)
{
var damage = power - this.Defense;
if (damage < 0f) damage = 0f;
this.Hp -= damage;
// 방송
if (this.Hp > 0f)
{
NotificationCenter.Broadcast($"{this.GetType()}는 '{damage}' 데미지를 받았다. (현재 체력 : {this.Hp})");
}
else
{
NotificationCenter.Broadcast($"{this.GetType()}는 사망했다.");
}
}
public bool IsDead()
{
return this.Hp <= 0f;
}
}
Character 클래스를 상속받은 Player.cs, Monster.cs
public class Player : Character
{
public Player()
{
this.Hp = 1500f;
this.Defense = 40f;
this.Power = 100f;
}
}
public class Monster : Character
{
public Monster()
{
this.Hp = 1000f;
this.Defense = 50f;
this.Power = 50f;
}
}
테스트 케이스
대결 시작
using System;
using System.Collections.Generic;
class Program
{
private static List<string> records = new List<string>();
static void Main(string[] args)
{
// 구독 (이벤트 발생마다 텍스트 기록)
NotificationCenter.Subscribes += text => records.Add(text);
// 캐릭터 생성
Player player = new Player();
Monster monster = new Monster();
// 대결 시작
while (!player.IsDead() && !monster.IsDead())
{
if (!player.IsDead())
player.Attack(monster);
if (!monster.IsDead())
monster.Attack(player);
}
// 대결 결과
for (int i = 0; i < records.Count; i++)
{
Console.WriteLine(records[i]);
}
}
}
결과
..중략
Monster는 '50' 데미지를 받았다. (현재 체력 : 400)
Player는 '10' 데미지를 받았다. (현재 체력 : 1380)
Monster는 '50' 데미지를 받았다. (현재 체력 : 350)
Player는 '10' 데미지를 받았다. (현재 체력 : 1370)
Monster는 '50' 데미지를 받았다. (현재 체력 : 300)
Player는 '10' 데미지를 받았다. (현재 체력 : 1360)
Monster는 '50' 데미지를 받았다. (현재 체력 : 250)
Player는 '10' 데미지를 받았다. (현재 체력 : 1350)
Monster는 '50' 데미지를 받았다. (현재 체력 : 200)
Player는 '10' 데미지를 받았다. (현재 체력 : 1340)
Monster는 '50' 데미지를 받았다. (현재 체력 : 150)
Player는 '10' 데미지를 받았다. (현재 체력 : 1330)
Monster는 '50' 데미지를 받았다. (현재 체력 : 100)
Player는 '10' 데미지를 받았다. (현재 체력 : 1320)
Monster는 '50' 데미지를 받았다. (현재 체력 : 50)
Player는 '10' 데미지를 받았다. (현재 체력 : 1310)
Monster는 사망했다.
예제 설명
Player vs Monster 구도로 대결을 하였습니다. 이 둘은 공격을 주고 받을 때마다, NotificationCenter에 알려줍니다. 공격 소식을 구독 중인 records.Add(text) 행위를 함으로써 기록을 저장할 수 있습니다.
Publisher(제공자) : Player, Monster 클래스
Observer(관찰자) : NotificationCenter 클래스
Subscribe(구독) : 기록 행위를 하는 대리자 delegate (string text) { records.Add(text); }
패턴 사용 목적
한 번의 행위로 여러 결과 행위가 필요한 경우에 사용하면 유리합니다.
(ex. 몬스터 사망 => 사망 이펙트, 결과창, 기록, 보수 지급, 다음 스테이지 이동 처리, 다른 유저들에게 업적 달성 알림 처리 등 동시 처리)
주의
- 무분별한 구독 남용시, 디버깅하기가 어려워지며 이는 예외 상황을 초래할 수 있습니다. 옵저버 패턴은 싱글턴 패턴에 버금가는 편리함이 있기 때문에 너무 의존적이지 않고, 꼭 필요한 곳에서 사용하는 것이 좋겠습니다.
- 순서 보장이 되지 않습니다. 구독하고 있는 메소드의 순서가 보장되지 않음을 유의해야 합니다. 하나의 프로젝트에서 여러 작업자들이 사용한다면 순서 보장이 될까요? 우리의 프로그래밍은 절차지향입니다. :)
'Study' 카테고리의 다른 글
디자인 패턴, Singleton Pattern (단일 인스턴스 패턴) (0) | 2022.01.31 |
---|---|
디자인 패턴, Flyweight Pattern (경량 패턴) (0) | 2022.01.30 |
디자인 패턴, Build Pattern (생성 패턴) (2) | 2022.01.22 |
C#, 일반적인 Cast와 is, as 키워드 그리고 퍼모먼스 (0) | 2022.01.13 |
A* 알고리즘 구현 (Unity, C# 환경) (0) | 2021.12.26 |
댓글