
수업시간에 배운 Delegate를 실 사용하는 디자인 중 "관찰자"가 포함된 패턴으로 이해된다. 즉,

이런 느낌이라 볼 수 있다.
관찰자 패턴은 크게 관찰자와 주체로 나눌 수 있다
Subscriber라고도 한다. (구독하기 때문에?)즉, 관찰자는 Listener이다. 서버-클라 모델에서 서버를 생각하면 보다 쉽게 이해 할 수 있다.
관찰자는 주체의 상태에 의해 수동적으로 반응하는 개념이다.
사실 이것은 디자인이다. 뭔가 특별한 클래스나 함수를 쓰는 것은 아니다.
언어에서 제공하는 어떤 기능을 사용하여 할 것인지는 내가 정하기 나름이다.
Delegate를 배우며 이미 이러한 내용에 대한 언급을 강사님이 하셨지만, 직접적으로 관찰자 디자인이라는 표현은 쓰지 않으셨다.
아니, 오히려 Delegate를 이해하려면 관찰자 디자인적으로 설명하는 것이 이해가 빠를 것 같아 그렇게 이야기 하신게 아닌가 싶다.
Subject 에는 알람을 보낼 Subscriber 들을 관리할 필요가 있다. 이것 이외에는 Subscriber에서 구현되는 내용에 대해서는 일체 독립적이어야 한다. Subscriber 또한 Subject를 직접적으로 수정해야 하는 일이 발생시켜서는 안된다.Subscriber는 필시 subscriber와 unsubscriber (구독과 구취) 를 구현해주어야 한다. (솔직히 하다보면 그렇게 되기 마련 이었다..)Subscriber는 없애주자. 시나리오를 진행한 스크립트.
- 시나리오 설명
- 우편함에 우편이나 소포가 들어올때마다 핸드폰과 PC 에 알람을 보내는 프로그램.
- 우편함은 하던일 하고 있고, 핸드폰과 PC 는 알람을 받고 있음.
- 우편 및 소포를 받음
- 몇몇의 우편 및 소포를 꺼냄
- 너무 시끄러워서 PC 알람은 꺼버림
- 다시 몇몇의 우편 및 소포를 꺼냄
- 중간중간 뭐가 있는지 전체 리스트 확인
using System;
namespace ObserverPatternTest
{
class Program
{
static void Main(string[] args)
{
MailBox mailBox = new MailBox();
Subscriber subscriber01 = new Subscriber("핸드폰", mailBox);
Subscriber subscriber02 = new Subscriber("PC", mailBox);
mailBox.ShowAll();
mailBox.AddMail(new Post("지로영수증", "썼으니 돈 내야지? by 정부"));
mailBox.AddMail(new Box("개사료", 5));
mailBox.AddMail(new Post("위반과태료", "XXX 동네에서 불법 주차 하였습니다.\n과태로: 10만원"));
mailBox.AddMail(new Box("2L생수 6개", 12));
mailBox.AddMail(new Post("안내장", "고백 공격은 그 누구도 용서치 않을 것 입니다."));
mailBox.AddMail(new Box("레고", 2));
mailBox.AddMail(new Post("아침 인사", "안녕? 굳세고 좋은 아침. 내이름은 왈드"));
mailBox.AddMail(new Box("책", 3));
mailBox.ShowAll();
mailBox.RemoveMail(1);
mailBox.ShowAll();
Console.WriteLine($"!!! {subscriber02.Name}의 알람을 그만 받겠다 !!!");
subscriber02.Unsubscribe();
subscriber02 = null;
GC.Collect();
mailBox.RemoveMail(2);
mailBox.RemoveMail(0);
mailBox.ShowAll();
}
}
}
Subject인 주체를 구현한 내용. 즉, Mail 관련 데이터와 MailBox 에 대한 구현체.
// MailBox.cs
public enum MailType
{
Post,
Box
}
interface IMailInformation
{
void Show();
}
public class Mail
{
public MailType mailType
{
get;
protected set;
}
public string Subject { get; set; }
public Mail(string subject)
{
Subject = subject;
}
}
public class Post : Mail, IMailInformation
{
public string Body { get; set; }
public Post(string subject, string body) : base(subject)
{
mailType = MailType.Post;
Body = body;
}
public void Show()
{
Console.WriteLine("-------------------");
Console.WriteLine(Subject);
Console.WriteLine("===================");
Console.WriteLine(Body);
Console.WriteLine("--------------------");
}
}
public class Box : Mail, IMailInformation
{
public int Weight { get; set; }
public Box(string subject, int weight) : base(subject)
{
mailType = MailType.Box;
Weight = weight;
}
public void Show()
{
Console.WriteLine("-------------------");
Console.WriteLine(Subject);
Console.WriteLine("===================");
Console.WriteLine($"소포 무게: {Weight}");
Console.WriteLine("--------------------");
}
}
public class MailBox
{
public delegate void SubscriberDel<T>(string action, T mail);
public Mail[] Mails = new Mail[10];
private int Index = 0;
public event SubscriberDel<Mail> Subscribers;
public int AddMail(Mail mail)
{
Mails[Index] = mail;
Index++;
Subscribers?.Invoke("추가", mail); // 트리거 발생!
return Index - 1;
}
public void RemoveMail(int index)
{
Mail removedMail = Mails[index];
Mails[index] = null;
for (int i = index + 1; i < Mails.Length; i++) {
if (Mails[i] == null) break;
Mails[i - 1] = Mails[i];
}
Index--;
Mails[Index] = null;
Subscribers?.Invoke("삭제", removedMail); // 트리거 발생!
}
public void ShowAll()
{
Console.WriteLine("===========================");
if (Mails[0] == null)
{
Console.WriteLine("우편함이 비어 있습니다.");
}
else
{
Console.WriteLine("우편함 목록은 아래와 같습니다.");
Console.WriteLine("---------------------------");
}
for (int i = 0; i < Mails.Length; i++)
{
if (Mails[i] == null) break;
Console.WriteLine($"{i}:[{Mails[i].mailType}]: {Mails[i].Subject}");
}
Console.WriteLine("===========================");
}
}
관찰자(Subscriber) 를 구현한 내용.
간단히 알람을 보낸다고 구현한 내용이다.
Box 와 Post 가 서로 다른 출력물을 나게 하였다.
// Subscriber.cs
interface ISubscriber
{
void Subscribe();
void Unsubscribe();
}
public class Subscriber : ISubscriber
{
private MailBox _mailBox;
public string Name;
public Subscriber(string name, MailBox mailbox)
{
Name = name;
_mailBox = mailbox;
Subscribe(); // 일부러 기능을 보여주기 위해 method 로 분리하고 생성자에 포함했다.
}
public void Subscribe()
{ // 구독
_mailBox.Subscribers += GetNoti;
}
public void Unsubscribe()
{ // 구취
_mailBox.Subscribers -= GetNoti;
}
public void GetNoti<T>(string action, T mail) where T : Mail
{ // 알람을 받았을 때의 행동
if (mail is Box)
{
ShowBox(action, mail as Box);
}
else if (mail is Post)
{
ShowPost(action, mail as Post);
}
}
public void ShowBox(string action, Box box)
{
Console.WriteLine($">>>\n{Name}(이)가 알립니다.");
Console.WriteLine($"--- 아래 항목이 {action} 되었습니다. ---");
Console.WriteLine($"{box.Subject} | {box.Weight} kg\n<<<");
}
public void ShowPost(string action, Post post)
{
Console.WriteLine($">>>\n{Name}(이)가 알립니다.");
Console.WriteLine($"--- 아래 항목이 {action} 되었습니다. ---");
Console.WriteLine($"{post.Subject}\n{post.Body}\n<<<");
}
}
===========================
우편함이 비어 있습니다.
===========================
>>>
핸드폰(이)가 알립니다.
--- 아래 항목이 추가 되었습니다. ---
지로영수증
썼으니 돈 내야지? by 정부
<<<
>>>
PC(이)가 알립니다.
--- 아래 항목이 추가 되었습니다. ---
지로영수증
썼으니 돈 내야지? by 정부
<<<
>>>
핸드폰(이)가 알립니다.
--- 아래 항목이 추가 되었습니다. ---
개사료 | 5 kg
<<<
>>>
PC(이)가 알립니다.
--- 아래 항목이 추가 되었습니다. ---
개사료 | 5 kg
<<<
>>>
핸드폰(이)가 알립니다.
--- 아래 항목이 추가 되었습니다. ---
위반과태료
XXX 동네에서 불법 주차 하였습니다.
과태로: 10만원
<<<
>>>
PC(이)가 알립니다.
--- 아래 항목이 추가 되었습니다. ---
위반과태료
XXX 동네에서 불법 주차 하였습니다.
과태로: 10만원
<<<
>>>
핸드폰(이)가 알립니다.
--- 아래 항목이 추가 되었습니다. ---
2L생수 6개 | 12 kg
<<<
>>>
PC(이)가 알립니다.
--- 아래 항목이 추가 되었습니다. ---
2L생수 6개 | 12 kg
<<<
>>>
핸드폰(이)가 알립니다.
--- 아래 항목이 추가 되었습니다. ---
안내장
고백 공격은 그 누구도 용서치 않을 것 입니다.
<<<
>>>
PC(이)가 알립니다.
--- 아래 항목이 추가 되었습니다. ---
안내장
고백 공격은 그 누구도 용서치 않을 것 입니다.
<<<
>>>
핸드폰(이)가 알립니다.
--- 아래 항목이 추가 되었습니다. ---
레고 | 2 kg
<<<
>>>
PC(이)가 알립니다.
--- 아래 항목이 추가 되었습니다. ---
레고 | 2 kg
<<<
>>>
핸드폰(이)가 알립니다.
--- 아래 항목이 추가 되었습니다. ---
아침 인사
안녕? 굳세고 좋은 아침. 내이름은 왈드
<<<
>>>
PC(이)가 알립니다.
--- 아래 항목이 추가 되었습니다. ---
아침 인사
안녕? 굳세고 좋은 아침. 내이름은 왈드
<<<
>>>
핸드폰(이)가 알립니다.
--- 아래 항목이 추가 되었습니다. ---
책 | 3 kg
<<<
>>>
PC(이)가 알립니다.
--- 아래 항목이 추가 되었습니다. ---
책 | 3 kg
<<<
===========================
우편함 목록은 아래와 같습니다.
---------------------------
0:[Post]: 지로영수증
1:[Box]: 개사료
2:[Post]: 위반과태료
3:[Box]: 2L생수 6개
4:[Post]: 안내장
5:[Box]: 레고
6:[Post]: 아침 인사
7:[Box]: 책
===========================
>>>
핸드폰(이)가 알립니다.
--- 아래 항목이 삭제 되었습니다. ---
개사료 | 5 kg
<<<
>>>
PC(이)가 알립니다.
--- 아래 항목이 삭제 되었습니다. ---
개사료 | 5 kg
<<<
===========================
우편함 목록은 아래와 같습니다.
---------------------------
0:[Post]: 지로영수증
1:[Post]: 위반과태료
2:[Box]: 2L생수 6개
3:[Post]: 안내장
4:[Box]: 레고
5:[Post]: 아침 인사
6:[Box]: 책
===========================
!!! PC의 알람을 그만 받겠다 !!!
>>>
핸드폰(이)가 알립니다.
--- 아래 항목이 삭제 되었습니다. ---
2L생수 6개 | 12 kg
<<<
>>>
핸드폰(이)가 알립니다.
--- 아래 항목이 삭제 되었습니다. ---
지로영수증
썼으니 돈 내야지? by 정부
<<<
===========================
우편함 목록은 아래와 같습니다.
---------------------------
0:[Post]: 위반과태료
1:[Post]: 안내장
2:[Box]: 레고
3:[Post]: 아침 인사
4:[Box]: 책
===========================
사실, 일반 Class 로 구현 및 생성자나 함수로 받아 직접 배열로 관리하거나 하는 방법도 있긴 하다. 하지만 그렇게 되면 한쪽 코드를 수정할 때 다른 쪽 코드를 수정해야 하는 케이스도 자주 발생하게 된다. 또한 관찰자에 대한 관리도 많이 복잡해 진다.
Delegate 는 이러한 독립성과 복잡성을 쉽게 해결해 주는 강력한 기능이다.
그럼에도 굳이 안 쓸 이유가..? 😆