개발자

“이벤트 발생시 동작 최적화” 옵저버블 설계 패턴의 이해

Rafael del Nero | InfoWorld 2023.01.02
옵저버블(Observable) 설계 패턴은 많은 자바 API에 사용된다. 잘 알려진 사례 중 하나로 ActionListener API를 사용해 동작을 실행하는 JButton이다. JButton에서 ActionListener는 버튼 클릭을 대기하거나 관찰한다. 버튼이 클릭 되면 ActionListener가 동작을 수행한다. 옵저버블 패턴은 리액티브(reactive) 프로그래밍에도 사용된다. 리액티브의 본질은 반응, 즉 다른 프로세스가 발생할 때 무엇인가 일어나는 것이므로 리액티브 애플리케이션과 옵저버는 서로 잘 맞는다. 
 
ⓒ Getty Image Bank

이처럼 옵저버블은 행동 설계 패턴으로, 그 기능은 이벤트가 발생할 때 동작을 수행하는 것이다. 대표적인 활용 사례는 버튼 클릭과 알림이지만 그 외에도 많은 용도가 있다. 
 

옵저버블 패턴의 예 

옵저버블 패턴에서 동작이 수행되면 한 객체가 다른 객체에 이를 알린다. 예를 들어 <그림 1>과 같이 버튼을 클릭해야 하고 다른 객체로의 알림이 없는 시나리오를 가정해 보자.
 
<그림 1> ActionCheck는 초당 버튼을 1번 체크한다. ⓒ IDG

여기서 ActionCheck는 초당 한 번씩 버튼을 확인해야 한다. 만약 이 버튼에 대해 동작 확인이 매초 여러 번 수행되면 애플리케이션 성능에 좋지 않은 영향을 줄 것이 뻔하다. 이때는 Do Something 버튼이 ActionCheck에 알리도록 하는 편이 훨씬 더 쉽다. 이렇게 하면 ActionCheck 로직은 매초 Do Something 버튼을 폴링할 필요가 없다. 
 

옵저버블 설계 패턴의 요소 

<그림 2> 다이어그램에서 옵저버 패턴의 기본이 Observer 인터페이스(관찰하는 객체)와 Subject(관찰되는 객체)임을 알 수 있다. Newsletter 클래스는 Subject를 구현하고 Subscriber는 Observer를 구현한다. 마지막으로 SendEmailMain이 옵저버블 설계 패턴을 실행한다. 
 
<그림 2> 구독자 예제에서 옵저버블 설계 패턴의 흐름 ⓒ IDG
 

옵저버블 패턴 코드

Subject 인터페이스(Observable 또는 Publisher라고도 함)는 옵저버블 설계 패턴의 기반이다. 이 인터페이스는 옵저버를 저장하고, 주시하는 동작이 발생하면 즉시 옵저버에 알린다. Subject 인터페이스를 보자. 
 
public interface Subject {

    void addSubscriber(Observer observer);
    void removeSubscriber(Observer observer);
    void notifySubscribers();

}

Observer 인터페이스 
Observer 인터페이스(Subscriber라고도 함)는 동작이 수행되었는지를 관찰하려는 구독자에 의해 구현된다. 
 
public interface Observer {

    public void update(String email);

}
 

옵저버블의 실제 사용 예 

뉴스레터 예제를 사용해 Subject 인터페이스를 구현해 보자. 다음 코드에서는 옵저버(여기서는 뉴스레터 구독자)를 저장하고, 구독자의 이메일이 구독에 추가될 때 각 구독자에게 알림을 보낸다. 
 
import java.util.ArrayList;
import java.util.List;

public class Newsletter implements Subject {

    protected List<Observer> observers = new ArrayList<>();
    protected String name;
    protected String newEmail;

    public Newsletter(String name) {
        this.name = name;
    }

    public void addNewEmail(String newEmail) {
        this.newEmail = newEmail;
        notifySubscribers();
    }

    @Override
    public void addSubscriber(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void removeSubscriber(Observer observer) {
        observers.remove(observer);
    }

    @Override
    public void notifySubscribers() {
        observers.forEach(observer -> observer.update(newEmail));
    }
}

Subscriber 
Subscriber 클래스는 이메일 뉴스레터를 구독하는 사용자를 나타낸다. 이 클래스는 Observer 인터페이스를 구현한다. 이벤트가 발생했는지를 알기 위해 이 객체를 관찰한다. 
 
class Subscriber implements Observer {

  private String name;

  public Subscriber(String name) {
    this.name = name;
  }

  @Override
  public void update(String newEmail) {
    System.out.println("Email for: " + name + " | Content:" + newEmail);
  }

}

SendEmailMain 
이제 옵저버블 패턴이 실질적으로 작동하도록 하는 주 클래스가 있다. 먼저 Newsletter 객체를 만든다. 그다음 구독자를 추가하고 제거한다. 마지막으로 이메일을 추가하고 구독자에게 구독자의 상태를 알린다. 
 
public class SendEmailMain {

  public static void main(String[] args) {
    Newsletter newsLetter = new Newsletter("Java Challengers");

    Observer duke = new Subscriber("Duke");
    Observer juggy = new Subscriber("Juggy");
    Observer dock = new Subscriber("Moby Dock");

    newsLetter.addSubscriber(duke);
    newsLetter.addNewEmail("Lambda Java Challenge");
    newsLetter.removeSubscriber(duke);

    newsLetter.addSubscriber(juggy);
    newsLetter.addSubscriber(dock);
    newsLetter.addNewEmail("Virtual Threads Java Challenge");
  }

}

코드의 출력은 다음과 같다. 
 
Email for: Duke | Content:Lambda Java Challenge
Email for: Juggy | Content:Virtual Threads Java Challenge
Email for: Moby Dock | Content:Virtual Threads Java Challenge
 

옵저버블 패턴이 적합한 경우 

동작이 일어나고 여러 객체가 그에 대한 알림을 받아야 한다면 Object의 상태를 여러 번 확인하는 것보다 옵저버블 패턴을 사용하는 편이 더 낫다. 200개 이상의 객체가 알림을 수신해야 하는 상황을 생각해 보자. 200에 확인 작업의 횟수를 곱해야 한다. 이때 옵저버블 패턴을 사용하면 알림은 모든 구독자를 대상으로 한 번만 수행된다. 성능 측면에서 막대한 이득이고 효과적인 코드 최적화이기도 하다. 손쉽게 확장 또는 변경하는 것도 가능하다.

리액티브 프로그래밍 패러다임은 모든 곳에서 옵저버블 패턴을 사용한다. 앵귤러를 사용해 작업한 적이 있다면 옵저버블 구성요소 사용이 매우 보편적임을 알 것이다. 리액티브 구성요소는 다른 이벤트 및 로직에 의해 관찰되는 경우가 많은데, 특정 조건이 충족되면 구성요소가 일정한 동작을 실행한다.
 

결론 

옵저버블 설계 패턴에 대해 기억해야 할 중요한 사항은 다음과 같다. 
 
  • 옵저버블은 개방-폐쇄 원칙을 사용한다. 즉, addSubscriber와 removeSubscriber 메서드를 메서드 서명을 변경하지 않고 확장할 수 있다. 직접 구현이 아니라 Subject 인터페이스를 수신하기 때문이다. 
  • Observer 인터페이스는 Subject에서 발생하는 모든 동작을 관찰한다. 
  • Subject를 Observable이라고도 한다. 관찰되는 대상이기 때문이다. 이벤트를 게시하므로 Publisher라고 하는 경우도 있다. 
  • Observer는 Subject/Publisher를 구독하므로 Subscriber라고도 한다. 동작이 발생하면 Observer는 알림을 받는다. 
  • 옵저버블 설계 패턴을 사용하지 않는다면 Subscriber는 이벤트가 발생했는지를 알기 위해 지속적으로 폴링을 해야 하고 이는 애플리케이션 성능에 매우 좋지 않은 영향을 미칠 것이다. 옵저버블이 더 효율적인 솔루션이다. 

editor@itworld.co.kr
Sponsored

회사명 : 한국IDG | 제호: ITWorld | 주소 : 서울시 중구 세종대로 23, 4층 우)04512
| 등록번호 : 서울 아00743 등록발행일자 : 2009년 01월 19일

발행인 : 박형미 | 편집인 : 박재곤 | 청소년보호책임자 : 한정규
| 사업자 등록번호 : 214-87-22467 Tel : 02-558-6950

Copyright © 2024 International Data Group. All rights reserved.