Java 옵저버 패턴 (Observer Pattern)

🗓 ⏰ 소요시간 15 분

하나 혹은 여러 클래스가 어떤 클래스의 상태 변화에 따라 동작해야하는 경우를 생각해봅시다.

예를 하나 들어보겠습니다. 제가 요즘 재밌게 보고 있는 페이지 중 ‘책 끝을 접다’ 라는 페이지가 있습니다. 책 소개해주는 페이지인데 흥미진진하게 소개를 해줘서 책을 사고 싶게 만드는 걸로 유명합니다. 저는 이 페이지에 신규 컨텐츠가 등록되었는지 종종 들어가서 확인해보곤 하는데요, 언제 신규 컨텐츠가 나오는지 모르면 궁금할 때마다 혹은 필요할 때마다 수시로 들어가서 확인해야 합니다.

하지만 ‘구독하기’ 기능을 이용하면 신규 컨텐츠가 등록될 때마다 카카오톡으로 이모티콘이 오기 때문에 바로 확인할 수 있습니다. 이처럼 신규 컨텐츠 등록이라는 상태를 내가 직접 확인하는 것이 아니라, 책끝을접다 페이지에서 구독자들의 카카오톡 정보를 리스트로 가지고 신규 컨텐츠가 등록될 때 모든 구독자에게 카카오톡을 전송하는 방식으로 보다 효율적으로 구성할 수 있는 것이 옵저버 패턴입니다.

여기서 상태를 가지고 있는 책끝을접다 페이지를 주체 객체 혹은 Observable 객체라고 하고, 상태 정보를 필요로 하는 구독자들을 Observer 객체라고 합니다. Observer 는 '관찰자’로 번역되므로 ‘상태를 관찰하고 있는’ 클래스라고 보시면 되고, Observable 객체는 ‘관찰되어지는’ 객체라고 보시면 되겠습니다.

구조

핵심은 주체 객체와 옵저버 객체의 결합도를 느슨하게 유지하는 것입니다.

https://en.wikipedia.org/wiki/Observer_pattern

  • 주체 객체는 옵저버들의 리스트를 가지고 옵저버를 추가/삭제하는 메소드를 제공합니다.
  • 옵저버 추가/삭제 메소드를 이용해서 주체 객체의 상태를 구독하거나 해지할 수 있습니다.
  • 옵저버 인터페이스를 구현한 각 옵저버들은 update() 라는 메소드를 구현해야 합니다.
  • 주체 객체는 옵저버 객체가 추가되거나 삭제되더라도 영향을 받지 않습니다.
  • 상태 변화 시 주체 객체는 각 옵저버의 udpate() 메소드를 실행합니다.

예시 코드 구현

다시 예시로 돌아가서, 조금 더 알기 쉽게 코드로 구현해보겠습니다.

다음은 책끝을접다 클래스인데 옵저버 패턴을 적용하지 않으면 is신규컨텐츠등록 메소드를 이용해 구독자들이 일일이 확인을 해야 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class 책끝을접다 implements Observable {

List<Observer> observerList = new ArrayList<>();

private boolean 신규컨텐츠등록;

public boolean is신규컨텐츠등록() {
return 신규컨텐츠등록;
}

public void 신규컨텐츠나왔다() {
신규컨텐츠등록 = true;
}
}

그렇다면 Observer 와 Observable 인터페이스를 작성해보겠습니다.

1
2
3
4
5
6
7
8
public interface Observable {

void 구독하기(Observer o);

void 구독해지(Observer o);

void 구독자들에게알리기();
}
1
2
3
4
public interface Observer {

void 업데이트();
}

주체 객체가 구현할 Observable 인터페이스에는 옵저버들을 추가/삭제하는 메소드와 각 옵저버들에게 상태 변화를 알리는 메소드를 가지고 있습니다. 옵저버가 구현해야 할 Observer 인터페이스는 상태변화에 따른 로직을 구현할 메소드를 가지고 있습니다. 이제 인터페이스들을 이용해서 옵저버 패턴을 적용해보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class 책끝을접다 implements Observable {

List<Observer> observerList = new ArrayList<>();

private boolean 신규컨텐츠등록;

public boolean is신규컨텐츠등록() {
return 신규컨텐츠등록;
}

public void 신규컨텐츠나왔다() {
System.out.println("신규 컨텐츠가 등록되었습니다.");
신규컨텐츠등록 = true;
구독자들에게알리기();
}

@Override
public void 구독하기(Observer o) {
observerList.add(o);
}

@Override
public void 구독해지(Observer o) {
observerList.remove(o);
}

@Override
public void 구독자들에게알리기() {
observerList.forEach(Observer::업데이트);
}
}

구독자를 간단하게 두 개 정도 만들어봤습니다.

1
2
3
4
5
6
7
public class 라이언 implements Observer {

@Override
public void 업데이트() {
System.out.println("라이언: 책 끝을 접다 재밌다!");
}
}
1
2
3
4
5
6
7
public class 무지 implements Observer {

@Override
public void 업데이트() {
System.out.println("무지: 콘한테 책 끝을 접다 공유해야지!");
}
}

그럼 테스트를 통해서 상태 변화에 따라 옵저버들의 로직이 자동으로 실행되는 것을 확인해보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
책끝을접다 페이지 = new 책끝을접다();
Observer 구독자1 = new 라이언();
Observer 구독자2 = new 무지();

페이지.구독하기(구독자1);
페이지.구독하기(구독자2);

페이지.신규컨텐츠나왔다();

페이지.구독해지(구독자2);

페이지.신규컨텐츠나왔다();

라이언과 무지가 구독을 한 후에 신규컨텐츠가 나오자 각각의 업데이트 메소드가 실행되었습니다. 또한 무지가 구독을 해지하자 신규 컨텐츠 등록 시 라이언에게만 정보가 가는 것을 확인할 수 있었습니다.

1
2
3
4
5
6
신규 컨텐츠가 등록되었습니다.
라이언: 책 끝을 접다 재밌다!
무지: 콘한테 책 끝을 접다 공유해야지!

신규 컨텐츠가 등록되었습니다.
라이언: 책 끝을 접다 재밌다!

Java 에서 제공하는 옵저버 패턴

옵저버 패턴은 간단하고 유용하게 쓰일 수 있는 패턴이라 자바에서도 기본으로 제공하고 있습니다. 바로 Observable 클래스와 Observer 인터페이스인데요, 이를 이용하면 좀 더 쉽게 구현할 수 있습니다.

하지만 위 예시에서 인터페이스를 이용해 따로 구현한 이유도 있습니다. 바로 Observable 객체가 클래스이다 보니 다중상속이 불가능하기 때문에 다른 클래스를 상속받고 있다면 사용할 수 없게 됩니다. 이럴 때는 직접 구현해서 사용하는 것이 더 좋겠죠.

정리

옵저버 패턴은 주체의 상태에 따라서 해당 상태 정보를 구독하는 옵저버들에게 연락이 가고 자동으로 내용이 업데이트 되는 방식입니다. 이를 이용해서 주체와 옵저버 간의 의존을 약하게 유지하면서 다양한 상태에 따른 로직을 유연하게 구현할 수 있습니다.