개발 기록
> [GOF Design Pattern] 행동 패턴 : 옵저버 (Observer) 패턴 - 개념과 구현 본문

* 행동 패턴 : 클래스와 객체들이 상호작용하는 방법과 역할을 분담하는 방법을 다루는 패턴
■
0. 개요

상황 1: 길동이는 제품이 입고 됬는지 확인하기위해 매번 가게에 방문한다.
가게 사장님은 무의미한 발걸음을 하는 불쌍한 길동이를 위해 제품이 입고 됬을 때 안내 메일을 발송해 주기로 했다.
상황 2: 가게 사장님은 길동이뿐만 아니라 입고에 관심없는 고객에게까지 입고 메일을 발송해서 고객들을 화나게 했다.
여기서 사장님은 선택해야 할 것은 두가지이다. 고객들을 화나게 하면서까지 길동이에게 메일을 발송해야할지, 아니면 길동이가 헛걸음 하게 냅둘지 말이다. 둘다 좋은 선택은 아닌 것같다. 그러면 이 상황을 어떻게 해결할 수 있을까? 바로 구독 매커니즘을 가진 옵저버 패턴을 활용하는 것이다.
★구독 매커니즘 ★
: 디자인 패턴이나 시스템 아키텍처에서 이벤트 기반 통신을 구현하는 방법 중 하나로 Publish-Subscribe (Pub-Sub) 모델이라고도 한다.
*이벤트를 생성하는 주체( Publish ) <-> 이벤트를 구독하여 처리하는 주체( Subscribe )
■
1. 옵저버(Observer) 패턴 개념
(1) 개념
옵저버 패턴은 주체(Subject)가 상태 변화를 통보하면 여러 옵저버들(Observer)이 그 변화를 감지하고 각각의 작업을 수행하는 디자인 패턴이다.
* observer 의 뜻이 관찰자라는 의미이긴 하지만, 사실은 이 패턴에서는 '관찰' 하기 보단 갱신을 위한 힌트 정보를 '전달' 받는 수동적인 역할을 한다.
* 이벤트 처리 시스템에서 일반적으로 사용된다 (=여러 개체가 다른 개체의 변경 사항에 반응해야 하는 시나리오)
☞ 장점
① 브로드 캐스트 통신: "브로드캐스트" 통신 메커니즘을 활성화. (대상의 상태가 변경되면 등록된 모든 관찰자에게 자동 통보)
② 재사용성: subject과 observer를 독립적으로 재사용할 수 있다.
③ 개방 폐쇄 원칙: 주체 객체를 변경하지 않고도 새로운 옵저버를 추가할 수 있다.
④ 느슨한 결합: 상태를 변경하는 객체(Subject)와 변경을 감지하는 객체(Observer)의 관계를 느슨하게 유지할 수 있다.
☞ 단점
① 구독자는 알림 순서를 제어할수 없고, 무작위 순서로 알림을 받는다. (하드 코딩으로 구현은 되지만, 복잡성과 결합성만 높아진다.)
② 흐름 이해의 복잡성, 디버깅의 어려움
③ 메모리 누수 가능성: observer가 subject에 등록된 상태에서 observer 객체를 제거하지 않는다면 메모리 누수가 발생할 수도 있다.
=> 옵저버의 등록 해제를 명시적으로 관리해야 한다.
④ 성능문제: subject의 상태 변화가 빈번하게 발생하고 많은 observer가 등록되어 있다면, 모든 observer에게 일일이 알림을 보내는 과정에서 성능 문제가 발생할 수 있다.
⑤ 긴밀한 결합 가능성: observer가 subject의 내부 구현 세부 사항에 직접적인 의존성을 갖는 경우 발생할 수 있다.
⑥ 연속업데이트 가능성: observer가 subject의 상태를 수정하면 업데이트의 연쇄 반응이 발생할 수 있다
⑦ 예상치 못한 업데이트 순서: observer 들간의 의존 관계가 존재할 경우, subject이 이를 관리하지 않으면 예기치 않은동작이 발생한다.
(2) 구조

☞ Subject : 관찰 대상자를 정의하는 인터페이스. 관찰자 목록을 유지 관리한다.(ex. 관찰자 연결, 분리, 알림)
☞ ConcreteSubject : Subject 인터페이스 구현체
☞ Observer : 관찰자 인터페이스. Subject 객체에서 호출되는 정의한다.
☞ ConcreteObserver : Observer 인터페이스 구현체. 상태 변화를 받기 위해 Subject 에 자신을 등록하며, Subject 상태의 변화에 응답하기 위한 업데이트 메서드를 구현한다.
■
2. 구현
옵저버 패턴에서는 한개의 관찰 대상자(Subject)와 여러개의 관찰자(Observer A, B, C)로 일 대 다 관계로 구성되어 있다.
◆ 흐름: 옵저버 등록 -> 상태변경 -> 알림 수신 및 처리
(1) Observer 인터페이스 정의
▶ 이 인터페이스는 최소한 하나의 update 메서드를 선언해야 한다.
▶ update(): Subject에서 호출되는 메서드로, Observer에게 상태 변경을 알리는 역할을 한다. 이 메서드는 주로 Subject에서 전달되는 변경된 데이터를 매개변수로 받는다.
public interface Observer {
// 관찰된 객체의 상태가 변경될 때마다 update 메서드가 호출된다.
void update(String eventTxt);
}
(2) Subject 생성
▶ List<Observer> observers: 관찰자 목록. Observer 들을 리스트(=Colletion)로 관리한다.
▶ registerObserver(): 관찰자 리스트에 해당 관찰자 추가
▶ removeObserver(): 관찰자 리스트에 해당 관찰자 제거
▶ notifyObservers(): Subject 상태 변경시, Observer 들에게 이를 알린다.
▶ setEvent(): 상태 변경
* 주체(Subject) 가 여러 종류일 때는 인터페이스를 사용하면된다.
@Component
// 옵저버를 관리하며, 상태 변화를 옵저버에게 알린다.
public class Subject {
private List<Observer> observers = new ArrayList<>(); // 관찰자 목록
private String eventTxt;
public void registerObserver(Observer observer) {
observers.add(observer);
}
public void removeObserver(Observer observer) {
observers.remove(observer);
}
private void notifyObservers() {
for (Observer observer : observers) {
observer.update(eventTxt);
}
}
public void setEvent(String text) {
this.eventTxt = text;
notifyObservers();
}
}
(3) 옵저버 구현체 생성
▶ Observer들은 Subject가 발행한 현재 상태를 취득한다. Subject 에서 전달되는 변경된 데이터를 매개변수로 받는다.
▶ Observer 들은 Subject 으로부터 상태 변경 알림을 받으면, update 메서드를 통해 전달된 메시지를 기반으로 필요한 조치를 취한다.
public class ConcreteObserverA implements Observer{
@Override
public void update(String eventTxt) {
System.out.println("[ ConcreteObserverA ] received price update: " + eventTxt);
}
}
----------------------------------------------------------------------------
public class ConcreteObserverB implements Observer{
@Override
public void update(String eventTxt) {
System.out.println("[ ConcreteObserverB ] received price update: " + eventTxt);
}
}
(4) TEST
public class ObserverTest {
private final Subject subject;
public void test() {
// 1. 옵저버 등록(=Subject 에 등록)
ConcreteObserverA concreteObserverA = new ConcreteObserverA();
ConcreteObserverB concreteObserverB = new ConcreteObserverB();
subject.registerObserver(concreteObserverA);
subject.registerObserver(concreteObserverB);
// 2. 주체의 상태가 변경되면, setMessage 메서드가 호출되어 메시지를 변경한다.
// 3. 메시지가 변경되면 notifyObservers 메서드가 호출되고, 모든 등록된 옵저버들에게 상태 변화가를 알린다.
subject.setEvent("Event AAAAA");
subject.setEvent("Event BBBBB");
// 4. 옵저버 삭제
subject.removeObserver(concreteObserverA);
subject.setEvent("Event CCCCC");
}
}
실행 결과

■
3. 옵저버 순환 실행 문제
(1) 문제
옵서버 패턴이 많이 쓰인 시스템에서는 순환 실행을 막는 메카니즘이 필요하다. 이벤트가 한번 처리된 후에는 A가 이벤트를 다시 발생시키지 않도록 하는 것이 좋다.
(2) 해결 방법
1. 이벤트 처리 플래그 사용: 각 옵저버는 이벤트를 처리한 후에 처리가 완료되었음을 나타내는 플래그를 설정한다. 다음에 이벤트가 발생하면 옵저버는 플래그를 확인하고, 이벤트가 이미 처리되었으면 처리를 건너뛴다.
2. 이벤트 버스 또는 중재자 패턴 사용: 중재자를 통해 이벤트를 발행하고, 옵저버들은 중재자를 통해 이벤트를 구독한다. 중재자는 이벤트를 한 번만 발행하고, 이미 처리된 옵저버에게는 다시 이벤트를 발행하지 않는다.
3. 옵저버 자체에서 처리 상태 관리: 각 옵저버는 자체적으로 처리한 이벤트에 대한 상태를 관리하여 이미 처리된 이벤트에 대해 다시 처리하지 않도록 한다.
■
4. 옵저버 패턴 실제 사용 사례
(1) JAVA 의 Observer
java.util.Observer 인터페이스와 java.util.Observable 클래스를 사용해 자바에서 옵저버 패턴을 구현할 수 있지만 상속 제한, 스레드 안전성 등의 문제 때문에 java.util.concurrent.Flow API나 RxJava 같은 리액티브 프로그래밍 라이브러리를 사용하여 옵저버 패턴을 구현한다.
-프로젝트가 간단한 비동기 스트림 처리를 필요로 하고, 외부 라이브러리에 의존하지 않기를 원한다 => java.util.concurrent.Flow
-풍부한 기능과 다양한 통합이 필요하고, 복잡한 비동기 작업을 처리해야 한다. => RxJava
아래 예제에서는 RxJava를 사용하여 옵저버 패턴을 구현한 것이다. Observable을 사용하여 상태 변화를 통지하고, DisposableObserver를 사용하여 이를 구독한다.
▶ 의존성 추가
implementation 'io.reactivex.rxjava3:rxjava:3.1.3'
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.observers.DisposableObserver;
public class RxJavaObserverPatternDemo {
public static void main(String[] args) {
// Observable은 emitter를 통해 데이터를 발행하고 onComplete 호출하여 완료 신호를 보낸다.
Observable<String> subject = Observable.create(emitter -> {
emitter.onNext("First State Change");
emitter.onNext("Second State Change");
emitter.onComplete();
});
// 옵저버 정의
// 1. observer1
DisposableObserver<String> observer1 = new DisposableObserver<>() {
// 이벤트 수신시 사용하는 콜백 메서드: onNext, onError, onComplete
// Observable이 발행하는 각 데이터를 처리한다.
@Override
public void onNext(String s) {
System.out.println("Observer 1 received: " + s); // 발행된 데이터 처리
}
// Observable에서 발생한 에러를 처리한다.
@Override
public void onError(Throwable e) {
// 에러가 발생하면 데이터 스트림은 종료되며, onNext나 onComplete는 더 이상 호출되지 않는다.
e.printStackTrace(); // 에러 처리
}
// Observable이 더 이상 발행할 데이터가 없음을 알린다.
// Observable이 모든 데이터를 발행하고 완료되었을 때 호출된다.
@Override
public void onComplete() {
System.out.println("Observer 1 completed"); // 완료 처리
}
};
// 2. observer2
DisposableObserver<String> observer2 = new DisposableObserver<>() {
@Override
public void onNext(String s) {
System.out.println("Observer 2 received: " + s);
}
@Override
public void onError(Throwable e) {
e.printStackTrace();
}
@Override
public void onComplete() {
System.out.println("Observer 2 completed");
}
};
// Observer 등록: Observable이 발행하는 각 데이터가 두 Observer에 전달된다.
subject.subscribe(observer1);
subject.subscribe(observer2);
}
}
(2) Spring Framework 에서 사용하는 옵저버 디자인 패턴
① Spring 프레임워크에서 옵저버 패턴은 ApplicationContext의 이벤트 처리 기능을 구현하는 데 사용된다.
② Spring은 이벤트 처리를 활성화하기 위해 ApplicationEvent 클래스와 ApplicationListener 인터페이스를 제공한다.
③ Spring Bean 은 ApplicationListener를 구현하여 이벤트를 수신하며, 특정 이벤트 유형에 대한 처리를 정의할 수 있다.
④ 예를 들어, AbstractAuthenticationProcessingFilter의 successfulAuthentication 메서드는 인증에 성공한 경우에 호출되며, 이를 통해 사용자가 성공적으로 인증되었음을 나타내는 이벤트를 발생시킨다.
■
5. 사용 사례
① GUI 시스템: ex. 버튼 클릭에 응답하는 이벤트
② 라이브러리/프레임워크의 이벤트 처리: ex. Java Swing
③ 분산 시스템: 시스템의 한 부분이 상태를 변경하면 원격에 있는 관찰자에게 이를 알린다.
④ MVC 아키텍처: MVC의 Model과 View의 관계는 Observer 패턴의 Subject 역할과 Observer 역할의 관계에 대응된다.
⑥ 채팅 애플리케이션 , 주식 시장 애플리케이션, 로깅 및 모니터링 시스템
⑦ 소프트웨어 시스템의 사용자 정의 이벤트
■
6. 다른 패턴과의 관계
(1) 비슷한 개념의 패턴
① 책임 연쇄 패턴: 잠재적 수신자의 동적 체인을 따라 수신자 중 하나에 의해 요청이 처리될 때까지 요청을 순차적으로 전달한다.
② 중재자 패턴: 발신자와 수신자 간의 직접 연결을 제거하여 그들이 중재자 객체를 통해 간접적으로 통신하도록 강제한다.
③ 커맨드 패턴: 발신자와 수신자 간의 단방향 연결을 설립한다.
* 옵서버 패턴은 수신자들이 요청들의 수신을 동적으로 구독 및 구독 취소할 수 있도록 한다.
(2) 중재자 패턴과 옵저버 패턴
중재자 패턴과 옵저버 패턴은 각기 다른 목적과 구조를 가지고 있지만, 상호 보완적으로 사용될 수 있는데, 중재자가 옵저버 패턴을 사용하여 컴포넌트 간의 동적 연결을 관리하면 더 유연하고 확장 가능한 시스템을 구축할 수 있다.
- 중재자 (ChatMediator): 채팅 방을 관리하며, 메시지를 모든 사용자에게 전달하는 역할을 합니다. -> (publisher) 역할
- 컴포넌트 (User): 채팅방에 참여하는 사용자로, 중재자를 통해 메시지를 주고받습니다. -> 구독자(subscriber) 역할
- 중재자가 이벤트 기반으로 컴포넌트들을 제어하는 방식을 구현할 수 있다.
(2-1) 채팅 시스템 예제
▶ 중재자 - 채팅방 생성
: ChatRoom 클래스는 채팅 메시지를 관리하고, PublishSubject를 사용하여 메시지를 발행한다.
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.subjects.PublishSubject;
public class ChatRoom {
private final PublishSubject<String> messageSubject = PublishSubject.create();
public Observable<String> getMessageStream() {
return messageSubject;
}
public void sendMessage(String message) {
messageSubject.onNext(message);
}
}
▶ 사용자 - Observer 클래스
: User 클래스는 Observer<String> 인터페이스를 구현하여 채팅 메시지를 구독하고 처리한다.
import io.reactivex.rxjava3.core.Observer;
import io.reactivex.rxjava3.disposables.Disposable;
public class User implements Observer<String> {
private final String name;
public User(String name) {
this.name = name;
}
@Override
public void onSubscribe(Disposable d) {}
@Override
public void onNext(String message) {
System.out.println(name + " received message: " + message);
}
@Override
public void onError(Throwable e) {}
@Override
public void onComplete() {}
}
▶ TEST
: Main 클래스에서 채팅방과 사용자를 생성하고, 채팅방에 사용자를 등록한 후 메시지를 발송한다.
: 각 사용자는 채팅방에서 발송된 메시지를 수신하여 출력한다.
public class Main {
public static void main(String[] args) {
// 채팅방 생성
ChatRoom chatRoom = new ChatRoom();
// 사용자 생성
User user1 = new User("User1");
User user2 = new User("User2");
// 채팅방에 사용자 등록
chatRoom.getMessageStream().subscribe(user1);
chatRoom.getMessageStream().subscribe(user2);
// 메시지 발송
chatRoom.sendMessage("Hello, everyone!");
chatRoom.sendMessage("How are you?");
}
}
참고
https://www.geeksforgeeks.org/observer-method-design-pattern-in-java/?ref=header_search
'디자인 패턴' 카테고리의 다른 글
| > [GOF Design Pattern] 생성 패턴 : 팩토리 메서드(Factory Method) 패턴 - 개념과 구현 (0) | 2024.03.09 |
|---|---|
| > [GOF Design Pattern] 행동 패턴 : 커맨드 (Command) 패턴 - 개념과 구현 (0) | 2024.03.06 |
| > [GOF Design Pattern] 행동 패턴 : 전략 (Strategy) 패턴 - 개념과 구현 (6) | 2024.02.05 |
| > [GOF Design Pattern] 구조 패턴 : 프록시(Proxy) 패턴 - 개념과 구현 (0) | 2024.01.17 |
| > 디자인 패턴에 대해 알아보기 - 개념과 유형 (0) | 2024.01.17 |