옵저버 패턴

반응형

현재 구독 서비스를 개발하고 있습니다.
왜 뜬금없이 구독 서비스를 이야기하느냐 하면, 개발을 하다 보니 옵저버 패턴과 구조적으로 비슷하다는 느낌을 받았기 때문입니다.

옵저버 패턴은 어떤 주체의 상태 변화를 관찰자들이 지켜보고 있다가, 변화가 발생하면 그에 맞춰 반응하는 구조를 가집니다.
구독 서비스 역시 특정 대상에 대해 사용자가 구독 여부를 맺고, 대상의 상태나 이벤트가 발생했을 때 그 결과를 사용자에게 전달합니다. 이 흐름만 놓고 보면 두 개념은 꽤 닮아 있습니다.

하지만 여기서 조심해야 할 부분이 있습니다.
이 유사성이 단순한 구조적 유사성인지, 아니면 설계 패턴 관점에서 옵저버 패턴이라고 불러도 되는지는 쉽게 단정할 수 없기 때문입니다. 특히 실무에서 사용하는 구독 모델을 그대로 옵저버 패턴이라고 정의해버리면, 개념의 레벨을 혼동하고 있다는 인상을 줄 수 있습니다.

그래서 이런 의문이 들었습니다. "이 구조는 정말 옵저버 패턴일까?"
아니면 "옵저버 패턴과 닮아 보일 뿐, 전혀 다른 문제를 풀고 있는 걸까?"

쉽지 않은 주제입니다.
그래서 이번에는 단순히 이름을 붙이기보다는, 옵저버 패턴이 정확히 무엇을 해결하려는 패턴인지부터 다시 정리해보려고 합니다.

이제 본격적으로 옵저버 패턴에 대해 학습해봅시다.

역시나 왜 탄생했는지 부터 알아봅시다.

만약, subject의 상태가 변경이 될때마다 Objects들이 반응을 해야 한다면 어떻게 코드를 작성할 수 있을까요?
당연하다고는 말 못하겠지만 이런식으로 작성해야 할겁니다.

class Subject {
 List<Obsevr> obsevrs;
 String current = "";
 public void action() {
   for(Obsevr o: obsevrs) {
     if(current == "MOVE") {
       if(o instance of o1) {
          o1.change();
       }
       if(o instance of o2) {
          o2.change();
       
       }
     }
     
       ...
   }
  
 }
}

== 인 이유는 equals보다 가독성이 높다고 생각해 ==으로 작성하였습니다.
대충 요런식으로 작성할 수 있습니다. 이 코드같은 경우 object가 늘어 날 수록 Subject의 코드는 더러워질 수 밖에 없습니다. 위 코드 처럼 계속해서 if문을 작성해야 하기 때문이죠. 또한, 규칙이 변경하여 코드를 수정한다고 하면 전체를 다 변경을 해야 하는지도 모르겠습니다.
하지만 제일 큰 문제는 Subeject가 Object들이 어떻게 반응해야  되는지 알고 있어야 하는것이 문제입니다. 이런 구조에서는 확장이 어렵고 변경에 취약하고 코드가 빠르게 썩기 시작합니다.
바로 이 문제 때문에, 상태 변경과 반응 로직을 분리하기 위해 옵저버 패턴이 탄생하게 되었습니다.

그렇다면 옵저버 패턴은 어떻게 이 문제를 해결했을까?

일단 obsevr와 subject의 클래스를 생성해봅시다.

class Subject {


}
class Obsevr {


}

옵저버 패턴이 위 문제를 해결하기 위해서는 몇 가지 전재 조건이 필요합니다.
그 중 하나가 상태의 주인은 subject여야 한다라는 전재조건이 있습니다. 이는 subject가 observer들을 알고 있어야 한다는 뜻이겠죠.
그렇다면, 

import java.util.*;
class Subject {
 List<Obsevr> obsevrs = new ArrayList<>();
 
}

 

 

이 코드는 반드시 있어야 한다는 뜻입니다. 그렇다고 Subject에서 변경된 상태를 observer에게 전달한다면 위와 같은 문제가 또 다시 발생할 수 밖에 없겠죠? 그렇다면 Subject는 observers들을 알면서 Observer은 Subject을 모르게 할 수 있을까요? 이것이 바로 옵저버 패턴의 핵심입니다.

push 방식

옵저버 패턴은 책임이 중요한 패턴으로 알고 있습니다. 그렇다는 이야기는 observer의 상태 변경이 Subject에서 발생하면 안된다는 뜻이 되죠. 그렇다면 다음같은 코드가 될것입니다.

import java.util.*;
class Subject {
     List<Obsevr> obsevrs = new ArrayList<>();
     String state;
     
     public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(state);
        }
    }
}

이렇게 작성하게 되면 subject는 observer쪽으로 상태값을 전달만 해주면 됩니다. 요것이 옵저버 패턴 push 방식입니다.

옵저버 패턴에는 push말고 pull방식도 존재합니다. 

pull 방식

import java.util.*;
class Subject {
     List<Obsevr> obsevrs = new ArrayList<>();
     String state;
     
     public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update();
        }
    }
}

pull방식은 push와 달리 Observer는 Subject에 의존할 수 밖에 없는 구조를 가지게 됩니다.
왜냐하면 Subject의 상태를 가져와서 observer에 반영을 해줘야 하는데 상태를 가져오는 과정에서 의존성이 생기게 됩니다.

class Observer {
  Subject subject;
  
  public void update() {
    String state = subject.getState();
    if ("MOVE".equals(state)) {
       System.out.println("react to MOVE");
    }    
  }

}

결국 옵저버 패턴은 의존성에 대한 패턴이 아니라 책임을 어떻게 사용하는지에 대한 패턴으로 인식이 되어집니다.

구분 Push Pull
상태 전달 Subject가 직접 전달 Observer가 직접 조회
통지 방식 update(state) update()
Subject 책임 상태 관리 + 전달 상태 관리 + 통지
Observer 의존성 Subject 모름 Subject 참조 필요
결합도 낮음 가장 낮음
구현 난이도 쉬움 조금 복잡
적합한 경우 상태 단순 상태 복잡

결론

옵저버 패턴은 상태를 다른 객체에게 어떻게 전달을 할 수 있을지 말해주는 패턴이라고 생각합니다. 옵저버 패턴에는 Subject와 Observer라는 구성요소가 존재합니다. 즉, Subject에서 Observer로 상태를 전달해준다고 할 수 있습니다. 그렇다면, 어떤 방식으로 상태값을 전달을 할 수 있을까요? push 방식과 pull방식이 있습니다. push는 Subject에서 상태값을 Observer로 값을 밀어넣어준다는 특징을 가지고 있습니다. 또한 pull방식같은 경우 Observer에서 Subject의 값을 가져와서 세팅을 해준다는 차이점을 가지고 있습니다. 이 둘의 공통점은 상태에 대한 책임은 Subject에서 한다는 공통점을 가지고 있습니다. 이로써 옵저버 패턴은 상태에 대한 책임을 어떻게 가져가냐에 대한 차이가 있다고 생각합니다.

 

'개발 > 디자인패턴' 카테고리의 다른 글

퍼사드 패턴  (0) 2026.01.05
빌더 패턴  (0) 2025.12.29
디자인패턴과 안티 패턴  (1) 2025.12.25
팩토리 메소드 패턴  (0) 2025.12.22
템플릿 메소드 패턴  (0) 2022.03.19

댓글

Designed by JB FACTORY