1. 옵저버 패턴 (Observer Pattern)
1️⃣ 옵저버 패턴이란?
1) 개념
한 객체(Subject)의 상태(state)가 변경되면, 해당 객체에 등록된 여러 Observer 객체들에게 자동으로 통지(notify)하는 구조
⚠️ Java에서는 인터페이스 기반 설계로 구현하는 것이 일반적
2️⃣ Java에서의 역할 구조
1) Subject (관찰 대상)
- Observer 목록을 관리
register(),remove(),notifyObservers()제공
2) Observer (관찰자)
update()메서드를 정의
- Subject가 상태 변경 시 호출됨
3️⃣ 구조 다이어그램 (개념)
+----------------+
| Subject |
+----------------+
| + register() |
| + remove() |
| + notify() |
+----------------+
▲
|
+----------------+
| ConcreteSubject|
+----------------+
+----------------+
| Observer |
+----------------+
| + update() |
+----------------+
▲
|
+----------------+
|ConcreteObserver|
+----------------+
4️⃣ 코드 예시 (정석 구현)
1) Observer 인터페이스
public interface Observer {
void update(int state);
}2) Subject 인터페이스
public interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers();
}3) ConcreteSubject
import java.util.ArrayList;
import java.util.List;
public class ConcreteSubject implements Subject {
private List<Observer> observers = new ArrayList<>();
private int state;
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(state);
}
}
public void setState(int state) {
this.state = state;
notifyObservers();
}
}4) ConcreteObserver
public class ConcreteObserver implements Observer {
private String name;
public ConcreteObserver(String name) {
this.name = name;
}
@Override
public void update(int state) {
System.out.println(name + " notified. New state: " + state);
}
}5) 실행 코드
public class Main {
public static void main(String[] args) {
ConcreteSubject subject = new ConcreteSubject();
Observer observer1 = new ConcreteObserver("Observer1");
Observer observer2 = new ConcreteObserver("Observer2");
subject.registerObserver(observer1);
subject.registerObserver(observer2);
subject.setState(10);
}
}6) 출력
Observer1 notified. Newstate:10
Observer2 notified. Newstate:105️⃣ Java에서 중요한 포인트
1) 인터페이스 기반 설계
구현이 아닌 추상화에 의존 (DIP 원칙)
2) 느슨한 결합 (Loose Coupling)
Subject는 Observer의 구체 클래스 모름
→
Observer 타입만 알면 됨3) OCP (Open-Closed Principle)
Observer를 추가해도 기존 Subject 코드 수정 없음
6️⃣ Java 표준 라이브러리의 옵저버
- Java 8 이전
java.util.Observerjava.util.Observable- 상속 강제 (Observable은 클래스)
- 유연하지 않음
- 인터페이스 기반이 아님
➡️ 존재했지만 Java 9부터 deprecated 되었습니다.
이유
- 실무에서는 직접 구현하거나, 아래 기술을 사용
- Spring EventListener
- RxJava
- Reactor (Flux, Mono)
- 이벤트 버스 라이브러리
7️⃣ 실무에서의 대표 사용 예
1) Spring 이벤트 시스템
@EventListener
public void handleEvent(MyEvent event) {
...
}→ 내부적으로 옵저버 패턴 기반
2) GUI 이벤트 리스너
button.addActionListener(e -> {
System.out.println("Clicked!");
});→ 버튼 상태 변화 → 리스너 호출
8️⃣ 옵저버 vs Pub-Sub 차이 (Java 관점)
구분 | Observer | Pub-Sub |
연결 구조 | Subject가 Observer 직접 참조 | 중간에 메시지 브로커 존재 |
결합도 | 약하지만 직접 연결 | 완전 분리 |
예 | Swing 이벤트 | Kafka, Redis Pub/Sub |
9️⃣ 단점
- Observer가 많으면 성능 문제
- 순환 참조 위험
- 멀티스레드 환경에서 동기화 필요
2. 실습(ex07)
1️⃣ polling (패턴X 옵저버가 뭔지)
1) LotteMart.java
C:\workspace\java_lab\designapp\src\ex07\polling\LotteMart.java
package ex07.polling;
public class LotteMart {
private String value = null;
// 상태 확인
public String getValue() {
return value;
}
// 입고
public void received() {
value = "상품";
}
}2) Customer1.java
C:\workspace\java_lab\designapp\src\ex07\polling\Customer1.java
package ex07.polling;
public class Customer1 {
// 상품 있어?
public void request(LotteMart lotteMart) {
String value = lotteMart.getValue();
if (value != null) update(value);
}
// 있으면 알림 받기
public void update(String msg) {
System.out.println("손님1이 받은 알림 : " + msg);
}
}3) App.java
C:\workspace\java_lab\designapp\src\ex07\polling\App.java
package ex07.polling;
public class App {
public static void main(String[] args) {
LotteMart lm = new LotteMart();
Customer1 cus1 = new Customer1();
// 1. 마트에 상품 입고 확인
new Thread(() -> {
for (int i = 1; i < 11; i++) {
System.out.println("입고중...(" + i + "초)");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
lm.received();
System.out.println("입고완료!!");
}).start();
// 2. 손님이 풀링
}
}4) 결과

5) App.java
package ex07.polling;
public class App {
public static void main(String[] args) {
LotteMart lm = new LotteMart();
Customer1 cus1 = new Customer1();
// 1. 마트에 상품 입고 확인 (마트 스레드)
new Thread(() -> {
// 10초 대기
for (int i = 1; i < 11; i++) {
System.out.println("입고중...(" + i + "초)");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
// 입고 완료
lm.received();
System.out.println("입고완료!!");
}).start();
// 2. 손님이 풀링 (손님 스레드)
new Thread(() -> {
// 3초에 한번씩 상품 확인 요청
while (true) {
System.out.println("[손님] 상품 있어?");
cus1.request(lm);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}).start();
}
}6) 결과

2️⃣ push (옵저버 패턴)
1) Coustomer.java (인터페이스)
C:\workspace\java_lab\designapp\src\ex07\push\sub\Customer.java
package ex07.push.sub;
public interface Customer {
void update(String msg);
}2) Mart.java (인터페이스)
C:\workspace\java_lab\designapp\src\ex07\push\pub\Mart.java
package ex07.push.pub;
import ex07.push.sub.Customer;
public interface Mart {
// 1. 구독 등록
void add(Customer customer);
// 2. 출판(입고)
void received();
// 3. 알림 (구독자 update 호출)
void notify(String msg);
// 4. 구독 취소
void remove(Customer customer);
}
3) Cus1.java
C:\workspace\java_lab\designapp\src\ex07\push\sub\Cus1.java
package ex07.push.sub;
public class Cus1 implements Customer{
@Override
public void update(String msg) {
System.out.println("손님1이 받은 알림 : " + msg);
}
}
4) LotteMart.java
C:\workspace\java_lab\designapp\src\ex07\push\pub\LotteMart.java
package ex07.push.pub;
import ex07.push.sub.Customer;
import java.util.ArrayList;
import java.util.List;
public class LotteMart implements Mart{
private String value = null; // 상품(1개로 전제)
// 임계영역(여러 스레드가 동시접근이 일어나게 될 때) -> 레이스 컨디션
private List<Customer> list = new ArrayList<>(); // 구독자 명단
// synchronized를 붙이면 하나의 스레드가 접근하면 다른 스레드가 접근하지 못하도록 막음 (레이스 컨디션 안 걸릴 수 있음)
@Override
synchronized public void add(Customer customer) {
list.add(customer);
}
@Override
public void received() {
value = "바나나"; // 입고 완료
notify(value);
}
@Override
public void notify(String msg) {
list.forEach(customer -> {
// 1. 구독에 따라 다르게 분기해줘야 함
customer.update(msg); // push
});
}
@Override
public void remove(Customer customer) {
list.remove(customer);
}
}
[참고] 크리티컬 섹션 (Critical Section, 임계영역)
[참고] 크리티컬 섹션 (Critical Section, 임계영역)5) Cus2.java
C:\workspace\java_lab\designapp\src\ex07\push\sub\Cus2.java
package ex07.push.sub;
public class Cus2 implements Customer{
@Override
public void update(String msg) {
System.out.println("손님2이 받은 알림 : " + msg);
}
}6) App.java
C:\workspace\java_lab\designapp\src\ex07\push\App.java
package ex07.push;
import ex07.push.pub.LotteMart;
import ex07.push.pub.Mart;
import ex07.push.sub.Cus1;
import ex07.push.sub.Cus2;
import ex07.push.sub.Customer;
/**
* push 방식 (옵저버패턴)
* 1. 구현방식 정해져 있음
* 2. 자바 lib : Reactive Java (자바9), Stomp(그 외)
* 3. 스프링 : Stomp(WebSocket 라이브러리)
*/
public class App {
public static void main(String[] args) {
// 1. 객체 생성
Mart lottemart = new LotteMart();
Customer cus1 = new Cus1();
Customer cus2 = new Cus2();
// 2. 구독
lottemart.add(cus1);
lottemart.add(cus2);
// 3. 구독취소
lottemart.remove(cus1);
// 4. 출판
new Thread(() -> {
lottemart.received();
}).start();
}
}
package ex07.push;
import ex07.push.pub.LotteMart;
import ex07.push.pub.Mart;
import ex07.push.sub.Cus1;
import ex07.push.sub.Cus2;
import ex07.push.sub.Customer;
/**
* push 방식 (옵저버패턴)
* 1. 구현방식 정해져 있음
* 2. 자바 lib : Reactive Java (자바9), Stomp(그 외)
* 3. 스프링 : Stomp(WebSocket 라이브러리)
*/
public class App {
public static void main(String[] args) {
// 1. 객체 생성
Mart lottemart = new LotteMart();
Customer cus1 = new Cus1();
Customer cus2 = new Cus2();
// 2. 구독
lottemart.add(cus1);
System.out.println("손님1 구독됨");
lottemart.add(cus2);
System.out.println("손님2 구독됨");
// 3. 구독취소
lottemart.remove(cus1);
System.out.println("손님1 구독 취소");
// 4. 출판
System.out.println("롯데마트 입고 시작");
lottemart.received();
}
}
Share article