들어가기 전에
Observer Pattern과 유사한 패턴이 Pub/Sub 패턴인 것 같은데 Observer Pattern을 사용했을 때 얻을 수 있는 장점과 단점으로 정리를 할까 합니다. 이 Observer Pattern이 어떻게 사용되는지 알면 좋을 것 같아서 Java 예제를 통해서 정리할 예정입니다.
1. Observer Pattern 개념
옵저버 패턴(Observer Pattern)은 객체의 상태 변화를 관찰하는 옵저버(관찰자)들의 목록을 객체에 등록하여 상태 변화가 있을 때마다 notify를 통해 객체가 직접 목록의 각 옵저버에게 통지하도록 하는 디자인 패턴
출처 : https://zerocodings.com/22#:~:text=%EC%98%B5%EC%A0%80%EB%B2%84%20%ED%8C%A8%ED%84%B4(Observer%20Pattern)%EC%97%90%EC%84%9C%EB%8A%94,to%2Dmany)%20%EC%9D%98%EC%A1%B4%EC%84%B1%EC%9E%85%EB%8B%88%EB%8B%A4.
간단한 정의는 위와 같은데 주체의 변화가 생길 때 이를 구독하는 옵저버에게 event를 실행시키는 패턴이다. 여러 방면에서 Observer Pattern을 사용할 수 있을 것 같은데 가장 비슷하고 대표적인 예가 Push Notification Event에 사용이 되거나 안드로이드 개발 시에 UI에 대한 event를 통해 상태 변화를 시킬 때 사용합니다.
그렇다면 Observer Pattern과 Pub/Sub Pattern은 같은 것인가?
결국에는 Publisher, Subscriber가 있는 것은 동일한데... 이런 생각이 들 수 있습니다.
Pub/Sub Pattern은 Observer Pattern과 달리 중개역할을 하는 broker가 있다는 것이 차이점입니다. 따라서 개인적인 생각으로는 하나의 시스템을 구축하는 경우 구독을 하는 모델이 어떤 event에 대해 반응을 해야 하는 경우에는 Observer Pattern이 MSA를 구축할 때는 Pub/Sub Pattern이 적절합니다.
2. Observer Pattern [Java]
2.1 Java 예제 코드
간단한 예제코드를 통해서 확인을 해볼건데 Publisher의 구현체를 하나 만든 뒤에 변경사항이 있을 때 출력하는 로직과 기본으로 출력하는 로직을 통해 console에서 확인을 할 예정입니다.
맨 처음에 위의 diagram에 해당하는 interface를 만들어줘야 합니다.
package com.example.designpattern.observer;
public interface Publisher {
public void add(Observer observer);
public void delete(Observer observer);
public void notifyUpdateObserver();
public void notifyObserver();
}
package com.example.designpattern.observer;
public interface Observer {
public void update(String title, String news);
public void send(String title, String news);
}
2개의 interface를 만든 뒤에 각각의 구현체를 만들어 주도록 하겠습니다. Observer의 구현체는 테스트 상 2개만 만들도록 하겠습니다.
package com.example.designpattern.observer;
import lombok.Getter;
import java.util.ArrayList;
@Getter
public class DailyPushNotification implements Publisher{
private ArrayList<Observer> observers = new ArrayList<>();
private String title;
private String message;
public DailyPushNotification(String title, String message) {
this.title = title;
this.message = message;
}
@Override
public void add(Observer observer) {
observers.add(observer);
}
@Override
public void delete(Observer observer) {
int index = observers.indexOf(observer);
observers.remove(index);
}
@Override
public void notifyUpdateObserver() {
for(Observer observer : observers) {
observer.update(title, message);
}
}
@Override
public void notifyObserver() {
for(Observer observer : observers) {
observer.send(title, message);
}
}
public void updateTitle(String title) {
this.title = title;
notifyUpdateObserver();
}
public void updateMessage(String message) {
this.message = message;
notifyUpdateObserver();
}
}
package com.example.designpattern.observer;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class FeedEventSubscriber implements Observer{
private String title;
private String message;
private Publisher publisher;
public FeedEventSubscriber(Publisher publisher) {
this.publisher = publisher;
publisher.add(this);
}
@Override
public void update(String title, String message) {
this.title = title;
this.message = message;
updateMessage(title, message);
}
@Override
public void send(String title, String message) {
log.info("[전송] {} 피드가 올라왔어요! {}", title, message);
}
public void updateMessage(String title, String message) {
log.info("[변경 감지] {} 피드가 업데이트 되었습니다. {}", title, message);
}
}
package com.example.designpattern.observer;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class BlogEventSubscriber implements Observer{
private String title;
private String message;
private Publisher publisher;
public BlogEventSubscriber(Publisher publisher) {
this.publisher = publisher;
publisher.add(this);
}
@Override
public void update(String title, String message) {
this.title = title;
this.message = message;
updateMessage(title, message);
}
@Override
public void send(String title, String message) {
log.info("[전송] {} 포스트가 올라왔어요! {}", title, message);
}
public void updateMessage(String title, String message) {
log.info("[변경 감지] {} 포스트가 업데이트 되었습니다. {}", title, message);
}
}
2.2 실행결과 확인
아래의 코드를 살짝 확인하면 기존 Publisher 구현체인 DailyPushNotifiaction 객체를 생성할 때 title, message 값이 각각 "일요일 아침 알림", "상쾌하게 보내세요!" 라는 값을 가지는 객체를 만듭니다.
그 뒤에 Observer 구현체인 BlogEventSubscriber, FeedEventSubscriber 객체를 생성할 때 DailyPushNotifiaction을 구독하면 됩니다.
밑에 출력 로직을 확인하면 기본적인 값을 출력했을 때와 title 또는 message가 변경이 생겼을 시 출력했을 때를 확인할 수 있습니다.
package com.example.designpattern;
import com.example.designpattern.observer.BlogEventSubscriber;
import com.example.designpattern.observer.DailyPushNotification;
import com.example.designpattern.observer.FeedEventSubscriber;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DesignPatternApplication {
public static void main(String[] args) {
SpringApplication.run(DesignPatternApplication.class, args);
DailyPushNotification dailyPushNotification = new DailyPushNotification("일요일 아침 알림", "상쾌하게 보내세요!");
BlogEventSubscriber blogEventSubscriber = new BlogEventSubscriber(dailyPushNotification);
FeedEventSubscriber feedEventSubscriber = new FeedEventSubscriber(dailyPushNotification);
dailyPushNotification.notifyObserver();
dailyPushNotification.updateTitle("토요일 아침 알림");
dailyPushNotification.notifyObserver();
dailyPushNotification.updateMessage("상쾌하게 즐기세요!");
}
}
2.3 결론
솔직히 예제처럼 Push Notification을 구현하기 위해 위처럼 Observer Pattern으로 만들지는 않습니다... 예제는 예제일 뿐이고 Observer 패턴은 객체간의 상태가 변경이 되고 이에 다른 객체의 동작을 트리거 해야될 때에서 보통 사용하게 됩니다.
Observer 패턴의 장점을 먼저 살펴보면 2가지의 장점이 있습니다.
OCP(개방 폐쇄 원칙)에 부합한 설계이기 때문에 기존의 코드를 수정하지 않고 기능을 새롭게 추가할 수 있다는 점과 DIP(의존관계 역전 원칙)에 따라 추상화(인터페이스)를 통해 종속성을 없애 느슨한 결합을 할 수 있다는 장점이 있습니다.
대신 이런 장점을 살리면서 Observer Pattern을 적용하기 위해서는 Java에 내장된 모델인 Observer와 Observable을 사용하지 않는 것이 좋습니다. Observer pattern을 쉽게 구현할 수 있지만 많은 문제점이 있어 Deprecated 되었습니다.
This class and the Observer interface have been deprecated. The event model supported by Observer and Observable is quite limited, the order of notifications delivered by Observable is unspecified, and state changes are not in one-for-one correspondence with notifications. For a richer event model, consider using the
java.beans package. For reliable and ordered messaging among threads, consider using one of the concurrent data structures in the java.util.concurrent package. For reactive streams style programming, see the Flow API.
- Java SE 9 문서의 Observable
일단 Observable이 Interface가 아닌 Class로 되어있습니다. 이는 느슨한 결합으로 구조를 만들 수 없고 Java에서 이미 상속을 받고 있는 Class에 대해서는 Observable class를 상속받을 수 없습니다.(단일상속) 또한 java.util 안에 있기 때문에 재구현이 안된다는 단점이 존재합니다.
단점은 알림의 순서를 제어할 수 없습니다. 또한 Observer Pattern이 복잡하게 얽혀 있는 경우 의도치 않은 문제(trigger)가 발생할 수 있으며 많은 Observer 객체를 등록한 이후 메모리를 해제하지 않는다면 memory leak 문제가 발생할 수 있습니다.
2.4 JS 예제
React에서 사용할 수 있는 Observer Pattern에 대한 설명과 예제가 나와있어 첨부드립니다.
물론 언어의 차이만 있을 뿐 기본적으로 작동하는 로직은 같습니다.
https://delivan.dev/react/programming-patterns-with-react-hooks-kr/
[번역] React Hooks로 알아보는 디자인 패턴
이 글은 Adam Hannigan이 작성한 Programming Patterns with React Hooks를 번역한 글입니다. 원 글에서는 ‘Programming Pattern’이라는 말을 사용하는데, ‘디자인 패턴’이라는 단어가 우리에게 더 친숙하고 의
delivan.dev
3. 참고 자료
GitHub - gyoogle/tech-interview-for-developer: 👶🏻 신입 개발자 전공 지식 & 기술 면접 백과사전 📖
👶🏻 신입 개발자 전공 지식 & 기술 면접 백과사전 📖. Contribute to gyoogle/tech-interview-for-developer development by creating an account on GitHub.
github.com
디자인패턴 - 옵저버 패턴(Observer Pattern)
정의 디자인 패턴 중 옵저버 패턴(Observer Pattern)을 알아보자. 객체지향 설계를 하다보면 객체들 사이에서 다양한 처리를 할 경우가 많다. 예를 들어 한 객체의 상태가 바뀔 경우 다른 객체들에게
flowarc.tistory.com
디자인 패턴 observer pattern(옵저버 패턴) 개념과 예제
Observer Pattern이란? ▶ 객체간 1:다 의존 관계를 정의한다. 한 개 객체 상태가 변경될 때, 그 객체와 의존 관계에 있는 모든 객체들이 자동으로 알림을 받고 상태를 갱신한다. 클래스 다이어그램과
dingdingmin-back-end-developer.tistory.com
https://johngrib.github.io/wiki/pattern/observer/
옵저버 패턴(Observer Pattern)
상태 변화를 감시자에게 통지한다
johngrib.github.io
💠 옵저버(Observer) 패턴 - 완벽 마스터하기
Observer Pattern 옵저버 패턴(Observer Pattern)은 옵저버(관찰자)들이 관찰하고 있는 대상자의 상태가 변화가 있을 때마다 대상자는 직접 목록의 각 관찰자들에게 통지하고, 관찰자들은 알림을 받아 조
inpa.tistory.com
https://forum.dotnetdev.kr/t/solid/1237
올바른 객체지향 설계 - solid 원칙이란?
객체지향 프로그래밍은 현대 프로그램 언어의 다수가 채택하고 있는 프로그래밍 개발 방법입니다. 로버트 마틴이란 분이 객체지향 프로그래밍 설계를 일관되게 할 수 있도록 2000년대 초반 5가
forum.dotnetdev.kr
'디자인패턴' 카테고리의 다른 글
Composite Pattern (0) | 2023.05.06 |
---|---|
Strategy Pattern (0) | 2023.05.06 |