옵저버 패턴
옵저버 패턴은, 옵저버들의 목록을 객체(관찰하려는 대상)에 등록하여
객체가 상태 변화가 있을 때 마다 메서드 등을 통해 객체가 직접 목록의 각 옵저버들에게 통지하는 디자인 패턴이다.
Observer(관찰자)
상태 변화를 감지하는 대상이다.
옵저버에는 함수나 객체 모두 등록이 가능하다.
Obervable(객체)
상태가 변경되는 대상이다.
subscribe, unsubscribe, notify 등 행동을 처리하는 메서드를 보유하고 있어야 한다.
옵저버 패턴의 핵심은 의존성을 낮추는 것(결합도를 낮추는 것)이다.
옵저버 패턴 구현을 위한 필수사항은 무엇인가?
1. 구독방법을 포함해야 한다.
2. 구독리스트를 담아야 한다.
3. 이벤트 발행하는 방법을 포함해야 한다.
그림을 통해 옵저버 패턴 알아보기
다음의 그림을 보면,
선생님 : Tom
학생 : Jane, Kane, Sane 이다.
미리 말하자면, 현재 선생님은 Observable이며 학생이 Observer이다.
1. 학생은 선생님의 모든 변화를 알아야 한다.
(= 선생님은 학생들에게 Notify 할 수 있어야 한다.)
그리고 학생은 자신의 상태를 업데이트 하는 기능을 가져야 한다.
2. 선생님은 학생을 등록, 등록해제, 알림할 수 있어야 한다.
그래서 선생님의 행동에 변화(객체에 변화)가 생길 때, notifyStudent 등과 같은 메서드를 통해 학생들에게 알려줘야 한다.
이를 위해서 선생님은 학생의 명단을 알아야 한다.
즉 선생님(객체)가 구독할 대상(학생)들을 등록할 수 있으며,
선생님(객체)는 자신이 변화할 때 마다 구독한 대상(학생)들에게 알림을 준다.
그리고 학생은 선생님으로부터 받은 알림이 오면, 각자 정의된 행동을 수행하게 된다.
옵저버 패턴에서의 호출 흐름
옵저버 패턴에서는 이벤트 중심으로 호출관계 흐름이 한 방향으로 진행된다.
예상할 수 있듯이, Observable에서 Observer로의 방향이다.
옵저버는 마치 구독 시스템의 push 알람처럼, 구독 대상의 변화에 의한 push 방식으로 데이터를 얻을 수 있다.
옵저버는 그저 구독 리스트에 등록만 되어 있다면, 옵저버블로부터 정보를 받아볼 수 있는 것이다.
옵저버패턴의 특징
옵저버 패턴에서는, 옵저버들이 구독하려는 대상(객체)를 알고 있으며, 객체들도 옵저버들의 목록을 알고 있다.
또한 객체 간 강한 결합(A의 상태 변화시 직접 B의 상태를 변경시키는 구체적인 코드)보다
객체 간의 관계를 느슨한 결합으로 만들 수 있으며
객체의 상태 변화를 옵저버에서 자동으로 알 수 있고
의존 관계가 1:N(객체 : 옵저버들) 이기 때문에 옵저버를 여러 개 만들 수도 있다.
옵저버 패턴은 대부분 동기방식으로 동작하도록 설계한다.
동기방식의 동작이란,
객체에서 이벤트가 발생하면 객체가 옵저버들의 목록을 순회하면서 옵저버의 메서드들을 순차적으로 호출한다는 것이다.
물론 옵저버 패턴을 이용하지 않고, 구독 시스템을 만들 수도 있다.
만약 Youtube라는 객체와 Tom, Jane, James라는 옵저버 객체가 있다면
class Youtube{
...
notifySubscriber(data){
tom.update(data);
jane.update(data);
james.update(data);
}
}
다음과 같은 구조를 지닐 수 있겠다.
이 때 예상 가능한 문제점으로는,
1. Youtube 객체와 각각의 옵저버 객체들은 강한 결합을 가진다.
2. Observer.update() 형태로 동일한 코드가 반복된다.
3. 새로운 옵저버 객체를 등록하기 위해서는 클래스를 직접 수정해야 한다.
이는 class 객체 내부에서 직접적으로 외부 객체를 사용하면서 발생하는 문제이기 때문에
이 관계를 느슨한 결합으로 만들어 줄 필요가 있다.
이 때 옵저버 패턴을 이용할 수 있다는 것이다.
느슨한 결합
느슨한 결합은 객체 내부에서 객체를 직접적으로 사용하는 의존성(=강한 결합)을 줄이려는 시도이다.
강하게 결합되어 있다는 의미는 A의 변화에 B도 맞춰서 변해야 한다는 의미이다.
A함수에서 문제가 발생해서 A함수를 고쳤는데, B 함수가 터진다는 말이다.
강한 결합은 객체의 유연성과 코드 재사용성을 떨어뜨린다.
위에서 다룬 Youtube 객체와 Observer 객체들은 현재 강한 결합을 가지고 있다.
Youtube 객체가 Observer의 메서드를 실행하기 위해서는 각 Observer들의 구현사항을 알고 있어야 한다.
그러므로 Observer들의 구현사항에 변화가 생긴다면 Youtube 객체가 그에 맞춰 바뀌어야 한다는 것이다.
객체가 느슨한 결합으로 상호작용한다는 것은, 서로에 대해 잘 모른다는 것을 의미한다.
물론, 잘 모른다는 것이지 아예 모른다는 것은 아니다.
기본적으로 두 객체가 소통하기 위한 공통 규약등을 정해야 하며, 인터페이스가 있는 언어에서는 인터페이스에 이를 정의하는 편이다.
느슨한 결합은 시스템의 유지보수를 더욱 용이하게 하고, 전체 프레임워크를 안정적으로 만들며 시스템 유연성을 증가시키곤 한다.
위의 youtube 예제를 느슨한 결합으로 만들어보자.
class Youtube{
constructor(){
this.observer=null;
}
register(observer){
this.observer=observer;
}
notifySubscriber(data){
this.observer.update(data);
}
}
class Observer{
update(data){
...
}
}
class Jane extends Observer{
}
다음과 같은 예시가 가능하겠다.
자바스크립트에서는 자바의 interface 개념이 없기 때문에,
Observer 클래스를 인터페이스처럼 활용했고,
Observer 클래스를 확장한 Jane 클래스와 Youtube 클래스의 관계가 느슨한 관계가 될 것이다.
자바 interface에서의 예를 잠시 들어보면
public interface Observer {
update(Data data);
}
public class Jane extends implements Observer{
public void update(Data data){
....
}
}
다음과 같이 인터페이스를 선언하고, 클래스에서는 해당 인터페이스를 구현하는 방식이다.
인터페이스에 선언된 메서드는 클래스에서 필수적으로 구현해야 하기 때문에
Observer 인터페이스를 Implement하는 모든 클래스(옵저버)들에 update가 공통규약으로서 구현 될 것이다.
그리고 코드를 더 발전시켜 옵저버 패턴을 적용해보자.
옵저버 패턴에서, 옵저버블과 옵저버는 1:N 관계를 보인다.
각 객체에 공통규약으로서 필요한 메서드는 다음과 같다.
옵저버블에는 구독, 구독취소, 알림 메서드를 정의하여 여러 옵저버를 다룰 수 있어야 한다.
옵저버는 옵저버블이 알림 메서드를 활용할 때, 알림을 받고 개별 동작을 수행할 수 있는 메서드를 정의해야 한다.
class Youtube{
constructor(){
this.observer=[];
}
subscribe(observer){
this.observer.push(observer);
}
unsubscribe(observer){
this.observer = this.observer.filter(obs => obs !== observer);
}
notifySubscriber(data){
this.observer.forEach(obs => obs.update(data));
}
}
class Observer{
update(data){
...
}
}
class Jane extends Observer{
}
정확히 동작하려면 구독/구독 해제 시 기존 구독 여부를 알아야 하는 등 추가적인 로직이 필요하지만, 아무튼 이렇다.
Youtube(옵저버블) 클래스는 구독, 구독해제, 알림 기능을 가지고 있다.
(notify에서 옵저버들의 메서들을 직접 실행했으나, 다른 방법으로도 구현할 수 있다.)
Jane(옵저버) 클래스는 옵저버 클래스를 확장하였으며 update 메서드를 가지고 있다.
update 메서드에는 Jane 클래스가 구독한 옵저버블의 상태변화가 일어났을 때 행동해야 할 로직이 담겨있겠다.
각 메서드들은 옵저버와 옵저버블의 역할을 수행하기에 최소한의 메서드들을 가지고 있으며,
추가적인 메서드들을 보유할 수도 있다.(당연히 보유할 것이다.)
그러나, 각각의 객체들은 공통 규약(정해진 메서드) 외에 상대의 구현사항을 알 필요가 없다.
이렇게 옵저버패턴을 이용하면 옵저버와 옵저버블 간 의존성을 낮출 수 있다.
옵저버 패턴에서는 주의할 점
옵저버와 옵저버블 간 모르는 상태가 아니다.
공통 규약을 통해 상대 객체에서 활용하는 메서드들을 알고 있으며,
옵저버블에 옵저버를 등록하는 첫 과정을 통해 서로 알고 있다고 할 수 있다.
옵저버블과 옵저버 간 서로 아예 모르는 패턴도 존재하는데,
구독-발행 패턴(pub-sub pattern)이 그러하다.
pub-sub 패턴은 기존 옵저버와 옵저버블의 사이에, 브로커 혹은 메시지 큐라는 중개자를 두고
옵저버 <-> 브로커 <-> 옵저버블 의 소통을 수행한다.
pub-sub 패턴에 대해서는 다음 포스팅에서 정리해 볼 예정이다.
참고한 출처
https://ko.wikipedia.org/wiki/옵서버_패턴
https://pjh3749.tistory.com/266
https://docs.microsoft.com/ko-kr/azure/architecture/patterns/publisher-subscriber
'Design Pattern' 카테고리의 다른 글
발행-구독 패턴(Publisher-Subscriber Pattern)이란? (0) | 2022.01.19 |
---|