Design Pattern

[디자인 패턴] 옵저버 패턴 (Observer Pattern)

꾸준함. 2024. 7. 2. 21:42

옵저버 패턴

  • 객체의 상태 변화를 감지하고 그 변화를 통지받는 다른 객체들에게 자동으로 전달하는 디자인 패턴
  • 주로 이벤트 기반 시스템이나 데이터 변경 통지를 구현할 때 유용
  • publish-subscribe 패턴을 구현할 수 있음

 

https://www.geeksforgeeks.org/observer-pattern-set-2-implementation/

 

주요 구성 요소

 

1. Subject

  • 상태 변화를 감지하는 객체로, 옵저버 객체들을 관리하고, 상태 변화가 있을 때 모든 옵저버들에게 알리는 역할

 

2. Observer

  • Subject 객체의 상태 변화를 통지받는 객체로, Subject 객체에 등록되어 있으며, Subject 객체의 상태가 변할 때 업데이트 메서드를 호출하는 역할

 

옵저버 패턴 구현 예시

  • 옵저버 패턴을 사용하여 크리켓 데이터를 관리하고 표시하는 시스템을 구현하는 예시
  • 용어 설명
    • wicket은 아웃된 타자의 수
    • overs는 현재 경기가 진행된 오버 수

 

1. Observer 인터페이스

 

public interface Observer {
void update(int runs, int wickets, float overs);
}
view raw .java hosted with ❤ by GitHub

 

2. Subject 인터페이스

 

public interface Subject {
void registerObserver(Observer o);
void unregisterObserver(Observer o);
void notifyObservers();
}
view raw .java hosted with ❤ by GitHub

 

3. ConcreteSubject

 

public class CricketData implements Subject {
private int runs;
private int wickets;
private float overs;
private List<Observer> observers;
public CricketData() {
observers = new ArrayList<>();
}
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
@Override
public void unregisterObserver(Observer o) {
observers.remove(o);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(runs, wickets, overs);
}
}
private int getLatestRuns() {
return 90;
}
private int getLatestWickets() {
return 2;
}
private float getLatestOvers() {
return 10.2f;
}
public void dataChanged() {
runs = getLatestRuns();
wickets = getLatestWickets();
overs = getLatestOvers();
notifyObservers();
}
}
view raw .java hosted with ❤ by GitHub

 

4. ConcreteObject

 

public class AverageScoreDisplay implements Observer {
private float runRate;
private int predictedScore;
@Override
public void update(int runs, int wickets, float overs) {
this.runRate = (float)runs / overs;
this.predictedScore = (int)(runRate * 50);
display();
}
public void display() {
System.out.println("평균 점수 및 예상 점수 출력");
System.out.println("Run Rate: " + runRate);
System.out.println("예상 점수: " + predictedScore);
}
}
view raw .java hosted with ❤ by GitHub

 

5. 클라이언트 코드

 

public class Client {
public static void main(String[] args) {
CricketData cricketData = new CricketData();
CurrentScore currentScore = new CurrentScore();
AverageScoreDisplay averageScoreDisplay = new AverageScoreDisplay();
cricketData.registerObserver(currentScore);
cricketData.registerObserver(averageScoreDisplay);
cricketData.dataChanged();
}
}
view raw .java hosted with ❤ by GitHub

 

부연 설명

  • ConcreteSubject인 CricketData는 데이터 변경 시 옵저버인 CurrentScore와 AverageScoreDisplay에 알림
  • 이를 통해 옵저버들은 주제의 상태 변화를 실시간으로 반영하여 데이터를 표시

 

옵저버 패턴 장단점

 

장점

  • 상태를 변경하는 객체인 publisher와 변경을 감지하는 객체(subscriber)의 관계를 느슨하게 유지 가능 (loosely coupled)
  • Subject의 상태 변경을 주기적으로 조회하지 않고 자동으로 감지 가능
  • 런타임에 옵저버를 추가하거나 제거할 수 있음

 

단점

  • 옵저버가 많아질수록 복잡도 증가
  • 다수의 옵저버 객체를 등록한 후 해제하는 작업을 거치지 않을 경우 메모리 누수 발생할 수 있음

 

실무에서 쓰이는 옵저버 패턴

 

1. 자바 8 버전까지 지원하는 Observable과 Observer

  • 자바에서 Observable과 Observer는 옵저버 패턴을 구현하기 위한 클래스로, java.util 패키지에 포함되어 있음
  • Observable은 Subject 역할을 하고, Observer는 옵저버 역할
  • 자바 9 버전부터는 Deprecated

 

2. Flow API

  • 비동기 스트림 처리와 반응형 프로그래밍을 지원하기 위한 API
  • 옵저버 패턴과 유사한 개념을 사용하여 데이터의 흐름을 관리하며, Subject와 Observer 간의 관계를 설정

 

Flow API 주요 구성 요소

  • Flow.Publisher<T>: Subject 역할을 하며, Flow.Subscriber에게 데이터를 발행
  • Flow.Subscriber<T>: Observer 역할을 하며, Flow.Publisher로부터 데이터를 수신
  • Flow.Processor<T, R>: Flow.Publisher와 Flow.Subscriber를 모두 구현하여 데이터의 중간 처리 역할
  • Flow.Subscription: Flow.Publisher와 Flow.Subscriber 간의 연결을 관리하며, 데이터의 요청과 취소를 처리

 

public class NewsPublisher extends SubmissionPublisher<String> {
public void publishNews(String news) {
System.out.println("뉴스 발행: " + news);
submit(news);
}
}
public class NewsSubscriber implements Flow.Subscriber<String> {
private Flow.Subscription subscription;
@Override
public void onSubscribe(Flow.Subscription subscription) {
this.subscription = subscription;
subscription.request(1); // 처음 요청 시 1개의 아이템 요청
}
@Override
public void onNext(String item) {
System.out.println("뉴스 수신: " + item);
subscription.request(1); // 다음 아이템 요청
}
@Override
public void onError(Throwable throwable) {
throwable.printStackTrace();
}
@Override
public void onComplete() {
System.out.println("모든 뉴스 수신 완료");
}
}
public class Client {
public static void main(String[] args) {
NewsPublisher newsPublisher = new NewsPublisher();
NewsSubscriber newsSubscriber = new NewsSubscriber();
newsPublisher.subscribe(newsSubscriber);
newsPublisher.publishNews("뉴스 1!");
newsPublisher.publishNews("뉴스 2");
newsPublisher.close();
}
}
view raw .java hosted with ❤ by GitHub

 

3. Spring 프레임워크 ApplicationContext와 ApplicationEvent

  • ApplicationContext: 스프링의 중앙 인터페이스로, 애플리케이션의 구성 정보를 제공하고 빈을 관리
    • ApplicationContext는 이벤트를 발행하고, 이벤트 리스너를 등록하여 이벤트를 처리하는 기능 제공

 

  • ApplicationEvent: 스프링에서 발생할 수 있는 이벤트의 기본 클래스
    • 모든 커스텀 이벤트는 헤당 클래스를 상속받아 정의

 

  • 정리하면 스프링 이벤트 모델에서는 ApplicationContext가 Subject 역할을 하고, ApplicationEvent가 이벤트, 그리고 이벤트 리스너가 Observer 역할

 

@Component
public class EventPublisher {
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
public void publishEvent(String message) {
CustomEvent customEvent = new CustomEvent(this, message);
applicationEventPublisher.publishEvent(customEvent);
}
}
public class CustomEvent extends ApplicationEvent {
private String message;
public CustomEvent(Object source, String message) {
super(source);
this.message = message;
}
public String getMessage() {
return message;
}
}
@Component
public class CustomEventListener {
@EventListener
public void handleCustomEvent(CustomEvent event) {
System.out.println("커스텀 이벤트 수신 - " + event.getMessage());
}
}
@SpringBootApplication
public class SpringEventDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringEventDemoApplication.class, args);
}
@Bean
CommandLineRunner commandLineRunner(ApplicationContext ctx) {
return args -> {
EventPublisher publisher = ctx.getBean(EventPublisher.class);
publisher.publishEvent("커스텀 이벤트!");
};
}
}
view raw .java hosted with ❤ by GitHub

 

참고

코딩으로 학습하는 GoF의 디자인 패턴 - 백기선 강사님

반응형