개요
옵저버 패턴(Observer Pattern)은 주체가 어떤 객체(subject)의 상태 변화를 관찰하다가 상태 변화가 있을 때마다 메서드 등을 통해 옵저버 목록에 있는 옵저버들에게 변화를 알려주는 디자인 패턴이다.
여기서 주체란 객체의 상태 변화를 보고 있는 관찰자이며, 옵저버들이란 이 객체의 상태 변화에 따라 전달되는 메서드 등을 기반으로 '추가 변화 사항'이 생기는 객체들을 의미한다.
또한 주체와 객체를 따로 두지 않고 상태가 변경되는 객체를 기반으로 구축되기도 한다.
옵저버 패턴은 여타 다른 디자인 패턴들과 다르게 일대다(one-to-many) 의존성을 가지는데, 주로 분산 이벤트 핸들링 시스템을 구현하는 데 사용된다. Pub/Sub(발행/구독) 모델로도 알려져 있기도 하다.

프로그래밍적으로 옵저버 패턴은 사실 '관찰'하기보단 갱신을 위한 힌트 정보를 '전달'받기를 기다린다고 보는 것이 적절하다. 관찰자라는 단어 뉘앙스에서 능동적으로 대상을 관찰하는 것처럼 느껴지지만, 사실 대상 객체로부터 수동적으로 전달받기를 기다리고 있기 때문이다.
옵저버 패턴 구조

- ISubject: 관찰 대상자를 정의하는 인터페이스
- ConcreteSubject: 관찰 당하는 대상자 / 발행자 / 게시자
- Observer들을 리스트(List, Map, Set ... 등)로 모아 합성(composition)하여 가지고 있다.
- 관찰자인 Observer들을 내부 리스트에 등록/삭제하는 인프라를 가지고 있다. (`register`, `remove`)
- Subject가 상태를 변경하거나 어떤 동작을 실행할 때, Observer들에게 이벤트 알림(notify)를 발행한다.
- IObserver: 구독자들을 묶는 인터페이스 (다형성)
- Observer: 관찰자 / 구독자 / 알림 수신자
- Observer들은 Subject가 발행한 알림에 대해 현재 상태를 취득한다.
- Subject의 업데이트에 대해 전후 정보를 처리한다.
옵저버 패턴은 여타 다른 디자인 패턴과 똑같이 상호작용할 객체를 합성을 하고 메서드 위임을 통해 구성하는 코드 패턴임은 똑같다. 하지만, 핵심은 합성한 객체를 리스트로 관리하고 리스트에 있는 관찰자 객체들에게 모두 메서드 위임을 통한 전파 행위를 한다.
옵저버 패턴 흐름
흐름

- 옵저버 패턴에서는 한 개의 관찰 대상자(Subject)와 여러 개의 관찰자(Observer A, B, C)로 일 대 다 관계로 구성되어 있다.
- Observer들은 언제든 Subject의 그룹에서 추가/삭제될 수 있다.
- Subject의 그룹에 추가되면 Subject로부터 정보를 전달받게 될 것이며, 그룹에서 삭제될 경우 더 이상 Subject의 정보를 받을 수 없게 된다.
- 관찰 대상 Subject의 상태가 바뀌면 변경사항을 옵저버한테 통보해준다.
- 대상자로부터 통보를 받은 Observer는 값을 바꾸거나 삭제하는 등 적절히 대응한다.
코드
클래스 구조
// 관찰 대상자 / 발행자
interface ISubject {
void registerObserver(IObserver o);
void removeObserver(IObserver o);
void notifyObserver();
}
class ConcreteSubject implements ISubject {
// 관찰자들을 등록하여 담는 리스트
List<IObserver> observers = new ArrayList<>();
// 관찰자를 리스트에 등록
@Override
public void registerObserver(IObserver o) {
observers.add(o);
System.out.println(o + " 구독 완료");
}
// 관찰자를 리스트에 제거
@Override
public void removeObserver(IObserver o) {
observers.remove(o);
System.out.println(o + " 구독 취소");
}
// 관찰자에게 이벤트 송신
@Override
public void notifyObserver() {
for(IObserver o : observers) { // 관찰자 리스트를 순회하며
o.update(); // 위임
}
}
}
// 관찰자 / 구독자
interface IObserver {
void update();
}
class ObserverA implements IObserver {
public void update() {
System.out.println("ObserverA 한테 이벤트 알림이 왔습니다.");
}
public String toString() { return "ObserverA"; }
}
class ObserverB implements IObserver {
public void update() {
System.out.println("ObserverB 한테 이벤트 알림이 왔습니다.");
}
public String toString() { return "ObserverB"; }
}
클래스 흐름
public class Client {
public static void main(String[] args) {
// 발행자 등록
ISubject publisher = new ConcreteSubject();
// 발행자를 구독할 관찰자들 리스트로 등록
IObserver o1 = new ObserverA();
IObserver o2 = new ObserverB();
publisher.registerObserver(o1);
publisher.registerObserver(o2);
// 관찰자에게 이벤트 전파
publisher.notifyObserver();
// ObserverB가 구독 취소
publisher.removeObserver(o2);
// ObserverA 한테만 이벤트 전파
publisher.notifyObserver();
}
}
실행 결과
ObserverA 구독 완료
ObserverB 구독 완료
ObserverA 한테 이벤트 알림이 왔습니다.
ObserverB 한테 이벤트 알림이 왔습니다.
ObserverB 구독 취소
ObserverA 한테 이벤트 알림이 왔습니다.
옵저버 패턴 특징
패턴 사용 시기
- 앱이 한정된 시간, 특정한 케이스에만 다른 객체를 관찰해야 하는 경우
- 대상 객체의 상태가 변경될 때마다 다른 객체의 동작을 트리거해야 할 때
- 한 객체의 상태가 변경되면 다른 객체도 변경해야 할 때. 그런데 어떤 객체들이 변경되어야 하는지 몰라도 될 때
- MVC 패턴
- MVC의 Model과 View의 관계는 Observer 패턴의 Subject 역할과 Observer 역할의 관계에 대응된다.
- 하나의 Model에 복수의 View가 대응한다.
패턴 장점
- Subject의 상태 변경을 주기적으로 조회하지 않고 자동으로 감지할 수 있다.
- 발행자의 코드를 변경하지 않고도 새 구독자 클래스를 도입할 수 있어 개방 폐쇄 원칙(OCP)을 준수한다.
- 런타임 시점에서 발행자와 구독 알림 관계를 맺을 수 있다.
- 상태를 변경하는 객체(Subject)와 변경을 감지하는 객체(Observer)의 관계를 느슨하게 유지할 수 있다. (느슨한 결합)
패턴 단점
- 구독자는 알림 순서를 제어할 수 없고, 무작위 순서로 알림을 받음
- 하드 코딩으로 구현할 수는 있겠지만, 복잡성과 결합성만 높아지기 때문에 추천되지는 않는 방법이다.
- 옵저버 패턴을 자주 구성하면 구조와 동작을 알아보기 힘들어져 코드 복잡도가 증가한다.
- 옵저버 객체가 많아질 수록 모든 옵저버 객체에 대한 처리를 해야하므로 성능 저하될 가능성이 있다.
- 다수의 옵저버 객체를 등록 이후 해지하지 않는다면 메모리 누수가 발생할 수도 있다.
자바 내장 옵저버 패턴
옵저버 패턴을 직접 구현할 수도 있지만 자바에서는 `java.util.Observable`(인터페이스), `java.util.Observer`(클래스)로 내장 옵저버 객체를 지원한다. 이 내장 객체를 사용하면 옵저버 패턴을 직접 구현할 필요없이 간단히 해당 클래스를 상속하기만 하면 옵저버 구조를 이용할 수 있다.
java.util.Observer
`Observer` 인터페이스를 `implements`한 클래스는 옵저버 클래스가 되게 된다. 옵저버 클래스들은 `update()` 추상 메서드를 구현함으로써, `Observable` 클래스로부터 데이터를 수신받게 된다.

- `update(Observable o, Object arg)`: `Observable`에게 전달받은 새로운 데이터를 갱신한다.
java.util.Observable
등록된 옵저버들을 관리하며, 새로운 데이터가 들어오면 옵저버에게 데이터를 전달하는 발행자 역할을 한다. 아래 클래스 구현부를 보면` `Vector` 컬렉션을 통해 옵저버들을 관리하는 것을 볼 수 있다. 또한 동기화(Synchronized)가 걸려있어 Thread-Safe하다.
이 `Observable` 클래스를 상속(extends)한 클래스는 발행자 역할로서 수행하게 된다.

- `addObserver(Observer o)`: 옵저버를 리스트에 추가
- `notifyObservers()`: 새로운 데이터가 들어오면 등록된 옵저버에 새로운 데이터와 파라미터를 전달
- `deleteObservers()`: 등록된 모든 옵저버를 제거
- `setChanged()`: 신규 데이터가 들어오면 `changed` 값을 `true`로 변경 (`changed` 변수가 `true`일때만 데이터가 옵저버에 전달된다.)
- `clearChanged()`: `changed` 값을 `false`로 변경한다. 옵저버에게 발행을 멈춘다.
- `hasChanged()`: 현재 `changed`의 값을 반환한다.
- `countObservers()`: 현재 등록되어 있는 옵저버의 수를 반환한다.
한계
1. Observable은 클래스이다.
인터페이스가 아니기 때문에 상속을 해야한다. 자바에서는 단일 상속만 지원하기 때문에 만일 발행자 역할을 해야 하는 클래스가 다른 클래스를 상속하고 있는 상태라면, `Observable` 클래스의 하위 클래스로 할 수 없게 된다.
인터페이스가 없다는 것은 해당 라이브러리를 커스텀하여 사용할 수 없다는 점도 있다.
2. Observable API는 Protected로 선언되었다.
그럼 합성(composition)을 통해 메서드 위임으로 구성해주면 되지 않을까 싶지만, `setChanged()` 메서드를 보면 Protected로 선언되어 있다. Observable의 자식 클래스에서만 `setChanged()`를 호출할 수 있다는 말이다.
즉, Observable를 구현한 주제 객체를 다른 패키지에서 인스턴스 변수로 써먹을 수 없다. 이런 디자인은 상속보다는 구성을 사용한다는 디자인 원칙에도 위배된다.
이런 단점들이 설계에 크게 영향을 미치지 않는다면 자바 내장 옵저버를 쓰는 방법도 괜찮다. 그렇지 않다면 옵저버 패턴을 직접 구현하면 된다.
예시
토픽이 메시지를 올리면 구독자들이 알림을 받아 출력
import java.util.ArrayList;
import java.util.List;
interface Subject {
public void register(Observer obj);
public void unregister(Observer obj);
public void notifyObservers();
public Object getUpdate(Observer obj);
}
interface Observer {
public void update();
}
class Topic implements Subject {
private List<Observer> observers;
private String message;
public Topic() {
this.observers = new ArrayList<>();
this.message = "";
}
@Override
public void register(Observer obj) {
if (!observers.contains(obj)) observers.add(obj);
}
@Override
public void unregister(Observer obj) {
observers.remove(obj);
}
@Override
public void notifyObservers() {
this.observers.forEach(Observer::update);
}
@Override
public Object getUpdate(Observer obj) {
return this.message;
}
public void postMessage(String msg) {
System.out.println("Message sended to Topic: " + msg);
this.message = msg;
notifyObservers();
}
}
class TopicSubscriber implements Observer {
private String name;
private Subject topic;
public TopicSubscriber(String name, Subject topic) {
this.name = name;
this.topic = topic;
}
@Override
public void update() {
String msg = (String) topic.getUpdate(this);
System.out.println(name + ":: got message >> " + msg);
}
}
public class HelloWorld {
public static void main(String[] args) {
Topic topic = new Topic();
Observer a = new TopicSubscriber("a", topic);
Observer b = new TopicSubscriber("b", topic);
Observer c = new TopicSubscriber("c", topic);
topic.register(a);
topic.register(b);
topic.register(c);
topic.postMessage("amumu is op champion!!");
}
}
/*
Message sended to Topic: amumu is op champion!!
a:: got message >> amumu is op champion!!
b:: got message >> amumu is op champion!!
c:: got message >> amumu is op champion!!
*/
자바: 상속과 구현
상속
자식 클래스가 부모 클래스의 메서드 등을 상속받아 사용하며 자식 클래스에서 추가 및 확장을 할 수 있는 것을 말한다. 이로 인해 재사용성, 중복성의 최소화가 이루어진다.
구현
부모 인터페이스를 자식 클래스에서 재정의하여 구현하는 것을 말하며, 상속과는 달리 반드시 부모 클래스의 메서드를 재정의하여 구현해야 한다.
상속과 구현의 차이
상속은 일반 클래스, abstract 클래스를 기반으로 구현하며, 구현은 인터페이스를 기반으로 구현한다.
Vue.js 3.0의 옵저버 패턴
프론트엔드에서 많이 쓰이는 프레임워크 Vue.js 3.0에서 ref나 reactive로 정의하면 해당 값이 변경되었을 때 자동으로 DOM에 있는 값이 변경되었는데, 이는 프록시 객체를 이용한 옵저버 패턴을 이용하여 구현한 것이다.
DOM(Document Object Model)
문서 객체 모델을 말하며, 웹 브라우저상의 화면을 이루고 있는 요소들을 지칭한다.
날씨 API로부터 온습도 알림 받기
interface Subject {
void registerObserver(Observer o); // 구독 추가
void removeObserver(Observer o); // 구독 삭제
void notifyObservers(); // Subject 객체의 상태 변경시 이를 모든 옵저버에게 알린다.
}
class WeatherAPI implements Subject{
float temp; // 온도
float humidity; // 습도
float pressure; // 기압
// 구독자들을 담아 관리하는 리스트
List<Observer> subscribers = new ArrayList<>();
void measurementsChanged() {
// 현재의 온습도 데이터를 랜덤값으로 얻는 것으로 비유하였다.
temp = new Random().nextFloat() * 100;
humidity = new Random().nextFloat() * 100;
pressure = new Random().nextFloat() * 100;
notifyObservers(); // 온습도 값이 변화하면 바로 옵저버들에게 발행
}
@Override
public void registerObserver(Observer o) {
subscribers.add(o);
}
@Override
public void removeObserver(Observer o) {
subscribers.remove(o);
}
// 이벤트 전파
@Override
public void notifyObservers() {
for(Observer o: subscribers) {
o.display(this); // 자신의 객체를 매개변수로 줘서 현재 자신의 상태를 구독자에게 알림
}
}
}
interface Observer {
void display(WeatherAPI api);
}
class KoreanUser implements Observer {
String name;
KoreanUser(String name) {
this.name = name;
}
public void display(WeatherAPI api) {
System.out.printf("%s님이 현재 날씨 상태를 조회함 : %.2f°C %.2fg/m3 %.2fhPa\n", name, api.temp, api.humidity, api.pressure);
}
}
public class Client {
public static void main(String[] args) {
WeatherAPI api = new WeatherAPI();
api.registerObserver(new KoreanUser("홍길동"));
api.registerObserver(new KoreanUser("임꺽정"));
api.registerObserver(new KoreanUser("세종대왕"));
// 온습도기에서 현재 상태의 온습도 정보가 갱신됨
// 알아서 전파되어 출력
api.measurementsChanged();
}
}
클라이언트에서 별다른 명령없이 알아서 자동으로 발행자로부터 구독자들이 데이터를 전달받아 적절히 자동으로 처리하는 것을 볼 수 있다.핵심은 구독자들의 관리를 발행자 객체에서 리스트로 한다는 점과 리스트에 있는 모든 구독자들의 메서드를 위임하여 실행함으로써 변화가 있을 때마다 이벤트성 알림으로 전파한다는 컨셉으로써 옵저버 패턴을 이해하면 된다.
내장 옵저버로 옵저버 패턴 구현
class WeatherAPI extends Observable {
float temp; // 온도
float humidity; // 습도
float pressure; // 기압
void measurementsChanged() {
// 현재의 온습도 데이터를 랜덤값으로 얻는 것으로 비유하였다.
temp = new Random().nextFloat() * 100;
humidity = new Random().nextFloat() * 100;
pressure = new Random().nextFloat() * 100;
/* 부모 클래스 Observable의 부모 메서드 */
setChanged(); // 내부 플래그를 true 로 만들어 알림이 동작하게 끔 한다
notifyObservers(); // 옵저버들에게 알림 전파
}
}
class KoreanUser implements Observer {
String name;
KoreanUser(String name) {
this.name = name;
}
public void display(WeatherAPI api) {
System.out.printf("%s님이 현재 날씨 상태를 조회함 : %.2f°C %.2fg/m3 %.2fhPa\n", name, api.temp, api.humidity, api.pressure);
}
@Override
public void update(Observable o, Object arg) {
// 발행자가 WeatherAPI 인 경우 (Observable을 상속한 모든 클래스에서 발행이 가능하니 구분해 주어야 한다)
if(o instanceof WeatherAPI) {
WeatherAPI w = (WeatherAPI) o; // 다운 캐스팅
display(w);
}
}
}
public static void main(String[] args) {
WeatherAPI api = new WeatherAPI();
// 옵저버 리스트 추가
api.addObserver(new KoreanUser("홍길동"));
api.addObserver(new KoreanUser("임꺽정"));
api.addObserver(new KoreanUser("세종대왕"));
// 온습도기에서 현재 상태의 온습도 정보가 갱신됨
// 알아서 전파되어 출력
api.measurementsChanged();
}
참조
'CS' 카테고리의 다른 글
| [CS] Proxy Server(프록시 서버) (0) | 2026.03.23 |
|---|---|
| [CS] Proxy Pattern(프록시 패턴) (0) | 2026.03.22 |
| [CS] Strategy Pattern(전략 패턴) (0) | 2026.01.07 |
| [CS] Factory Pattern(팩토리 패턴) (0) | 2026.01.03 |
| [CS] Singleton Pattern(싱글톤 패턴) (0) | 2025.12.17 |