
옵저버 패턴은 예전에도 공부를 했었지만, 많이 쓰이고 있고 앞으로도 쓸 일이 많을 것 같아서 ..
책을보고 다시 공부를 해봤다.
옵저버 패턴
옵저버 패턴은 신문사의 구독을 생각하면 편하다.
신문사들은 날마다 신문을 찍어내면 자기들의 구독자들에게 매일같이 신문을 보내준다.
이와 같이 주제(Subject)클래스(신문사)가 있고, 미리 이 주제에 대한 변화나 알림을 받아야 하는 옵저버들을 등록해놓는다.
그리고 어떠한 변화나 알림이 생기면 주제 클래스에서 옵저버들에게 일괄적으로 알림을 보내주는 구조다.
내가 만든 예시 - 환율이 바뀔때마다 알려주는 시스템
옵저버 패턴을 클래스로 구현해놓고 함수를 오버라이드해서 쓰게끔 구현해놓은 코드도 봤는데, 일반적으로는 인터페이스를 이용해서 많이 쓴다고 한다.
그래서 은행에서 환율이 바뀔때마다 자신들의 '환율 변동 시스템' 을 구독해놓은 구독자들에게 알림을 보내준다는 가정을 하고 대충 만들어 봤다.
주제 인터페이스
public interface ISubject
{
public void RegisterObserver(IObserver O);
public void RemoveObserver(IObserver O);
public void NotifyToObserver(float USD,float JPY);
}
Isubject 인터페이스는 옵저버 등록, 옵저버 삭제, 옵저버에게 알림 함수 3가지를 구현해야 한다.
옵저버 인터페이스
public interface IObserver
{
public void Update(float USD, float JPY);
}
Iobserver 인터페이스는 알림을 받을 Update함수만 구현을 하면 끝이다.
Bank 클래스
class Bank : ISubject
{
public List<IObserver> Observers = new List<IObserver>();
//환율 시스템 업데이트
public void GetExchangeRate(exchangeRate_System ex)
{
NotifyToObserver(ex.USD(), ex.JPY());
}
//구독자들에게 환율 변동 알려주기
public void NotifyToObserver(float USD, float JPY)
{
foreach(IObserver O in Observers)
{
O.Update(USD,JPY);
}
}
//구독 신청
public void RegisterObserver(IObserver O)
{
Observers.Add(O);
}
//구독 해제
public void RemoveObserver(IObserver O)
{
Observers.Remove(O);
}
}
Isubject를 상속받은 Bank클래스는 Iobserver형식의 리스트를 변수로 가지고 있다.
GetExchangeRate 함수를 실행시키면 exchangeRate_System이라는 클래스에서 달러와 엔화를 가져오고, 바로 구독자들에게 알려준다.
NotifyToObserver 함수는 등록된 옵저버 리스트를 전부 돌며 Update함수를 호출한다.
구독 신청, 해제 함수도 리스트에 추가, 삭제로 구현을 했다.
구독자들
class Person1 : IObserver
{
Bank bank;
public Person1(Bank bank)
{
this.bank = bank;
this.bank.RegisterObserver(this);
}
public void Update(float USD, float JPY)
{
Console.WriteLine($"사람1: 현재 USD는 {USD}원! ");
}
}
class Person2 : IObserver
{
Bank bank;
public Person2(Bank bank)
{
this.bank = bank;
this.bank.RegisterObserver(this);
}
public void Update(float USD, float JPY)
{
Console.WriteLine($"사람2: 현재 JPY는 {JPY}원! ");
}
}
Person1 과 2는 각각 생성자에서 bank클래스를 인자로 입력받아서 바로 구독자로 등록을 한다.
update함수에서는 각각 자기들이 필요한 USD와 JPY를 콘솔에 찍게 만들었다.
호출 해보기
class MainApp
{
static void Main(string[] args)
{
exchangeRate_System mySystem = new exchangeRate_System();
Bank myBank = new Bank();
Person1 p1 = new Person1(myBank);
Person2 p2 = new Person2(myBank);
myBank.GetExchangeRate(mySystem);
}
}
Bank클래스에서 GetExchangeRate 함수만 호출하더라도 person1, 2에게 알림이 가서 그에 맞는 행동을 한것을 볼 수 있다.
현재의 문제점.. Push & Pull
Person1과 2 클래스를 각각 살펴보면 Person1은 달러만 필요하고, Person2는 엔화만 필요하다.
하지만 NotifyToOberserver 함수와 Update함수의 매개변수를 보면 float USD와 float JPY가 있기 때문에, 함수를 호출할때 각 옵저버들에게 필요없는 인자까지 입력해야하는 경우가 생긴다.
현재는 주제 클래스에서 모든 옵저버들에게 일방적으로 데이터를 보내는 Push 방식이라면,
반대로 옵저버들이 주제 클래스에서 필요한 정보만 당겨오는 Pull 방식을 사용하면 조금 더 효율적이게 바뀔 것 같다.
인터페이스 수정
public interface ISubject
{
public void RegisterObserver(IObserver O);
public void RemoveObserver(IObserver O);
//매개변수 없앰
public void NotifyToObserver();
}
public interface IObserver
{
//매개변수 없앰
public void Update();
}
두 메소드의 매개변수를 없애 주었다.
Bank 클래스 수정
class Bank : ISubject
{
public List<IObserver> Observers = new List<IObserver>();
float USD;
float JPY;
public float GetUSD()
{
return USD;
}
public float GetJPY()
{
return JPY;
}
//환율 시스템 업데이트
public void GetExchangeRate(exchangeRate_System ex)
{
this.USD = ex.USD();
this.JPY = ex.JPY();
NotifyToObserver();
}
...밑에 생략
USD와 JPY의 게터 메소드를 생성 해주었고,
환율이 변동 되었을때 변수에 값들을 저장해주고 NotifyToBoserver를 아무런 인자 없이 호출을 해주었다.
옵저버 클래스 수정
class Person1 : IObserver
{
Bank bank;
public Person1(Bank bank)
{
this.bank = bank;
this.bank.RegisterObserver(this);
}
public void Update()
{
//매개변수가 없어졌으니 필요한 부분만 Bank 클래스에서 가져온다.
Console.WriteLine($"사람1: 현재 USD는 {bank.GetUSD()}원! ");
}
}
class Person2 : IObserver
{
Bank bank;
public Person2(Bank bank)
{
this.bank = bank;
this.bank.RegisterObserver(this);
}
public void Update()
{
//매개변수가 없어졌으니 필요한 부분만 Bank 클래스에서 가져온다.
Console.WriteLine($"사람2: 현재 JPY는 {bank.GetJPY()}원! ");
}
}
각 클래스의 업데이트마다 필요한 정보만 Subject 클래스에서 가져왔다.
실행을 해주면.. 전과 똑같이 작동 하는것을 볼 수 있다.
정리 & 느슨한 결합
객체지향에서 한 객체가 다른 객체에 너무 심하게 의존하면 그 객체가 다른 객체에 단단하게 결합이 되어있다고 한다.
단단하게 결합이 되어있으면 그만큼 재사용, 확장이 어렵다는 뜻이다.
하지만 이 옵저버 패턴은 그 반대인 '느슨한 결합' 을 보여주는 좋은 예시라고 한다.
주제 클래스에서는 단순히 자신이 맡은 업무인 '무언가가 변동 되었다' 라는 것만 구독자들에게 알려주면 되는거고, 구독자들이 이 알림을 듣고 뭘 하는지는 알 필요가 없다.
실제로 생각 해봐도 신문사에서 구독자들이 신문을 받고 읽는지, 바닥에 깔기 위해 구독을 하는지는 알기도 힘들고, 알 필요도 없는 일이다.
옵저버를 추가, 삭제도 가능하고 옵저버가 추가되거나 삭제되었다고 해서 주제를 바꿀 필요도 없다.
이것이 '느슨한 결합'의 위력이라고 한다.
간단하게 책을보고 공부한 옵저버 패턴을 내 방식대로 허접하게 수정해서 만들어 보았다.
예전에 공부할때도 느꼈지만 쓰일 일이 정말 많은 패턴같다.
지금 만드는 게임도 특정 상태마다 많은 객체들에게 알림을 보내줘야 하는 부분이 있어서 적용한 부분이 있다.
실제 적용까지 해보니 디자인 패턴의 중요성을 더더욱 느꼈고 꾸준히 공부를 해야겠다는 생각이 든다.
'프로그래밍 공부 > 디자인 패턴' 카테고리의 다른 글
싱글톤 패턴 vs 정적 클래스 (Singleton Pattern vs Static Class) (0) | 2024.02.29 |
---|---|
디자인 패턴 - <데코레이터 패턴> (Decorator Pattern) (0) | 2022.11.21 |
디자인 패턴 - <전략 패턴> (Strategy Pattern) (2) | 2022.11.14 |
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!