참고 자료
https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%EC%98%B5%EC%A0%80%EB%B2%84Observer-%ED%8C%A8%ED%84%B4-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EB%B0%B0%EC%9B%8C%EB%B3%B4%EC%9E%90
https://velog.io/@octo__/%EC%98%B5%EC%A0%80%EB%B2%84-%ED%8C%A8%ED%84%B4Observer-Pattern
00. 들어가기 전
원래 Observer Pattern에 대해서 알고는 있었으나, 최근에 로직을 구성하면서 관심사를 분리하여 결합을 낮출 수 있는 Spring Boot와 NestJS의 EventListner 기능을 애용하고 있는 데 이게 사실 Observer Pattern이라는 사실을 깨달아서 제대로 공부하며 글을 정리해본다!
01. Observer Pattern
옵저버 패턴은 옵저버들이 관찰하고 있는 대상의 상태 변화가 있을 때 마다 대상자들은 목록의 각 관찰자들에게 통지하고 관찰자들은 알림을 받아 조치를 취하는 행동 패턴이라고 한다.
이렇게 보면 어렵게 느껴지겠지만 우리가 흔히 사용하는 pub/sub 모델이라고 한다.
pub/sub 모델은 Publisher가 Event를 발행하면 Subscriber 그 Event를 받아서 처리하는 모델을 의미하고 현재 서버 시스템은 Kafka, RabbitMQ, Redis 등을 사용하여 이러한 모델을 사용하고 있다.
자 그러면 옵저버 패턴이란 어렵게 말하면 옵저버(관찰자, Subscriber)들이 관찰하고 있는 대상자(Publisher)의 상태가 변경(Event 발행)이 되면 각 목록의 관찰자(Subscriber)들에게 통지하여 이벤트를 처리하는 방식이다.
옵저버 패턴은 다른 디자인 패턴과 다르게 one to many의 의존성을 가지게 되는 데 분산 이벤트 핸들링 시스템을 구현하는 데 사용돈다고 한다.
해당 글은 Java를 기준으로 작성할 예정이다.
02. 패턴 구현
자 나는 관찰자의 인터페이스 Observer와 구현체 Subscriber, 관찰 대상자의 인터페이스 Subject, 구현체 Publisher를 만들어볼 거다.
public interface Observer {
void update();
}
class SubscriberA implements Observer {
@Override
public void update() {
System.out.println("Publisher has been updated.");
}
}
class SubscriberB implements Observer {
@Override
public void update() {
System.out.println("Publisher has been updated.");
}
}
자 Observer Interface는 update 메서드를 선언하고 구현체인 SubscriberA와 SubscriberB를 만듭니다. 사실 관찰자인 Observer의 예시는 별개 없습니다.
실무에선 해당 형태로 비즈니스 로직을 담으면 됩니다.
interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers();
}
class Publisher implements Subject {
List<Observer> observer;
@Override
public void registerObserver(Observer observer) {
this.observer.add(observer);
}
@Override
public void removeObserver(Observer observer) {
this.observer.remove(observer);
}
@Override
public void notifyObservers() {
for (Observer observer : this.observer) {
observer.update();
}
}
}
그리고 Subject 인터페이스에는 관찰자를 추가하는 registerObserver, 제거하는 removeObserer, 이벤트를 발행하는 notifyObservers를 선언하고 구현체인 Publisher를 만듭니다.
해당 코드를 확인하면 구현체인 Publisher가 observer를 List로 가지고 관리하는 걸 볼 수 있다.
class Run {
public static void main(String[] args) {
Publisher publisher = new Publisher();
Observer subscriberA = new SubscriberA();
Observer subscriberB = new SubscriberB();
publisher.registerObserver(subscriberA);
publisher.registerObserver(subscriberB);
publisher.notifyObservers();
}
}
자 이제 해당 옵저버 패턴을 실행을 하게되면 구독자와 옵저버 구현체 인스턴스를 생성한 후에 Publisher에 SubscriberA, SubscriberB의 인스턴스를 생성하여 넣어주고, NotifyObservers를 호출함으로 옵저버에 이벤트를 전달할 수 있다.
03. 옵저버 패턴의 장단점
먼저 옵저버 패턴의 장점으로는 크게 2가지가 있다.
- 실시간으로 한 객체의 변경 사항을 다른 객체에 전파할 수 있다.
- 느슨한 결합으로 유여한 시스템을 구성하고 객체 간의 의존성을 제거할 수 있다.
필자는 이 중에서 2번에 장점을 크게 느낀다. Spring Boot나 NestJS에서 사용하는 EventListener 기능을 사용하면서 더더욱 느끼는 데, 유여한 시스템을 구성하고 의존성을 제거할 수 있다는 것이 큰 거 같다.
예를 들어 특정 비즈니스 로직에서 Log 데이터를 저장한다고 할 때 나는 EventListenr를 이용하여 비즈니스 로직에서 관심사를 분리하고 의존성을 제거하여 구성한다. Log 데이터를 수집하는 과정에서 본 비즈니스 로직에 영향을 안 가게 하고 싶기도 하고 말이다.
단점으로는 크게 2가지가 있다.
- 너무 많이 사용할 경우 상태 관리가 힘들고 오히려 시스템이 복잡해진다.
- 데이터 배분에 문제가 큰 문제로 야기될 수 있다.
옵저버 패턴은 유연한 시스템 구성을 하게 해주지만 너무 많이 작성될 경우 해당 로직이 어느 이벤트로 가는 지 일일이 파악해야하며 이는 오히려 시스템이 복잡해지고 상태 관리가 어려워질 수 있기에 적당히, 컨벤션 내에서 사용하는 것이 좋을 거 같다.
04. 마치며..
다음에는 Express의 Event Listener와 Spring boot의 EventListener도 한 번 정리해봐야겠다.
그리고 Decorator Pattern을 사용하여 직접 EventListener도 구현해봐야겠다.
'CS > 자료구조' 카테고리의 다른 글
상대 패턴(State Pattern) (0) | 2024.04.13 |
---|---|
[자료 구조] Queue (0) | 2022.11.16 |
[자료 구조] Stack (0) | 2022.11.16 |
[CS] 진법에 대해 알아보자(1) (0) | 2022.08.14 |