
이처럼 옵저버블은 행동 설계 패턴으로, 그 기능은 이벤트가 발생할 때 동작을 수행하는 것이다. 대표적인 활용 사례는 버튼 클릭과 알림이지만 그 외에도 많은 용도가 있다.
옵저버블 패턴의 예
옵저버블 패턴에서 동작이 수행되면 한 객체가 다른 객체에 이를 알린다. 예를 들어 <그림 1>과 같이 버튼을 클릭해야 하고 다른 객체로의 알림이 없는 시나리오를 가정해 보자.
여기서 ActionCheck는 초당 한 번씩 버튼을 확인해야 한다. 만약 이 버튼에 대해 동작 확인이 매초 여러 번 수행되면 애플리케이션 성능에 좋지 않은 영향을 줄 것이 뻔하다. 이때는 Do Something 버튼이 ActionCheck에 알리도록 하는 편이 훨씬 더 쉽다. 이렇게 하면 ActionCheck 로직은 매초 Do Something 버튼을 폴링할 필요가 없다.
옵저버블 설계 패턴의 요소
<그림 2> 다이어그램에서 옵저버 패턴의 기본이 Observer 인터페이스(관찰하는 객체)와 Subject(관찰되는 객체)임을 알 수 있다. Newsletter 클래스는 Subject를 구현하고 Subscriber는 Observer를 구현한다. 마지막으로 SendEmailMain이 옵저버블 설계 패턴을 실행한다.
옵저버블 패턴 코드
Subject 인터페이스(Observable 또는 Publisher라고도 함)는 옵저버블 설계 패턴의 기반이다. 이 인터페이스는 옵저버를 저장하고, 주시하는 동작이 발생하면 즉시 옵저버에 알린다. Subject 인터페이스를 보자.void addSubscriber(Observer observer);
void removeSubscriber(Observer observer);
void notifySubscribers();
}
Observer 인터페이스
Observer 인터페이스(Subscriber라고도 함)는 동작이 수행되었는지를 관찰하려는 구독자에 의해 구현된다.
public void update(String email);
}
옵저버블의 실제 사용 예
뉴스레터 예제를 사용해 Subject 인터페이스를 구현해 보자. 다음 코드에서는 옵저버(여기서는 뉴스레터 구독자)를 저장하고, 구독자의 이메일이 구독에 추가될 때 각 구독자에게 알림을 보낸다.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 인터페이스를 구현한다. 이벤트가 발생했는지를 알기 위해 이 객체를 관찰한다.
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 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: 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