observer
는 관찰자라는 뜻이다. 즉, 옵저버 패턴은 관찰자 패턴으로, 객체의 상태가 변화하는지 관찰하는 패턴이다.
상태가 변할 수 있는 데이터 객체가 있고, 상태가 변하는 것을 관찰하는 옵저버 객체가 있다.
보통 인터페이스로 만들지만 좀 더 쉬운 이해를 위해서 클래스로 표현하려고 한다.
코드를 살펴보기 전에 뭐가 나올지만 먼저 이야기하자면, 관측자들에게 정보가 변화했다는 메시지를 보낼 Subject
클래스를 만들 것이고, 그 메시지를 수신할 Observer
클래스를 만들 것이다.
import java.util.ArrayList;
import java.util.List;
public class Subject {
private final List<Observer> observers;
public Subject() {
observers = new ArrayList<>();
}
public void addObserver(Observer observer) {
observers.add(observer);
System.out.println(observer.getName() + " 추가");
}
public void removeObserver(Observer observer) {
observers.remove(observer);
System.out.println(observer.getName() + " 제거");
}
public void notify(String message) {
for (Observer observer : observers) {
observer.update(message);
}
System.out.println("메시지 전파 완료 : " + message);
}
}
public class Observer {
private final String name;
public Observer(String name) {
this.name = name;
}
void update(String message) {
System.out.println(name + " : " + message + " 수신");
}
public String getName() {
return name;
}
}
Subject
클래스는 Observer
들을 여러 개 가지고 있고, notify()
를 통해 Observer
들에게 정보가 변화되었다는 것을 알리는 메시지를 보낸다. 그럼 각 Observer
는 메시지를 받아서 각자 처리한다. 클래스들을 직접 사용해 보자.
public class Main {
public static void main(String[] args) {
Subject subject = new Subject();
Observer observer0 = new Observer("옵저버 A");
subject.addObserver(observer0);
Observer observer1 = new Observer("옵저버 B");
subject.addObserver(observer1);
Observer observer2 = new Observer("옵저버 C");
subject.addObserver(observer2);
Observer observer3 = new Observer("옵저버 D");
subject.addObserver(observer3);
subject.notify("Hello world!");
}
}
실행 결과
옵저버 A 추가
옵저버 B 추가
옵저버 C 추가
옵저버 D 추가
옵저버 A : Hello world! 수신
옵저버 B : Hello world! 수신
옵저버 C : Hello world! 수신
옵저버 D : Hello world! 수신
메시지 전파 완료 : Hello world!
subject
객체에 옵저버들을 추가하고, 메시지를 하나 전송했더니 등록한 모든 옵저버가 메시지를 수신했다.
여기까지 잘 읽었다면 옵저버 패턴이 어떤 구조로 되어 있는지 감이 왔을 것이라고 생각한다. 이제 클래스 말고 인터페이스로 '진짜' 옵저버 패턴을 구현해 보자.
public interface Subject {
void addObserver(Observer observer);
void removeObserver(Observer observer);
void notify(String message);
void notify(int message);
}
public interface Observer {
void update(String message);
void update(int message);
}
Subject
와 Observer
를 인터페이스로 만들었다. int형 message
정보도 다룰 수 있도록 메소드를 추가했다. 이제 이 인터페이스들을 상속하는 클래스들을 구현하자.
import java.util.ArrayList;
import java.util.List;
public class Clock implements Subject {
private final List<Observer> observers;
public Clock() {
this.observers = new ArrayList<>();
}
@Override
public void addObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notify(String message) {
// do nothing
}
@Override
public void notify(int message) {
for (Observer observer : observers) {
observer.update(message);
}
}
public void clock() {
for (int time = 1; time <= 24; ++time) {
notify(time);
}
}
}
Subject
를 상속하는 시계 클래스를 만들었다. clock()
은 1시부터 24시까지 한 시간마다 알림을 보내는 함수이다.
import java.util.Objects;
public class Army implements Observer {
private final String[] todos = new String[25];
public Army() {
todos[ 1] = "불침번 기상";
todos[ 2] = "다음 번대 불침번 깨우기";
todos[ 7] = "아침 점호";
todos[ 8] = "아침 식사";
todos[ 9] = "오전 일과 시작";
todos[12] = "점심 식사";
todos[13] = "오후 일과 시작";
todos[17] = "체력 단련";
todos[18] = "저녁 식사";
todos[19] = "개인 정비";
todos[20] = "청소";
todos[21] = "저녁 점호";
todos[22] = "취침";
}
@Override
public void update(String message) {
// do nothing
}
@Override
public void update(int message) {
if (Objects.isNull(todos[message]))
return;
System.out.println("군인 : " + message + "시 - " + todos[message]);
}
}
Observer
를 상속하는 Army
클래스를 만들었다. 군인은 매 시간마다 시계를 보고 시간에 맞는 행동을 한다.
구현한 코드를 한 번 실행해 보자.
import Army;
import Clock;
public class Main {
public static void main(String[] args) {
Clock clock = new Clock();
Army army = new Army();
clock.addObserver(army);
clock.clock();
}
}
실행 결과
군인 : 1시 - 불침번 기상
군인 : 2시 - 다음 번대 불침번 깨우기
군인 : 7시 - 아침 점호
군인 : 8시 - 아침 식사
군인 : 9시 - 오전 일과 시작
군인 : 12시 - 점심 식사
군인 : 13시 - 오후 일과 시작
군인 : 17시 - 체력 단련
군인 : 18시 - 저녁 식사
군인 : 19시 - 개인 정비
군인 : 20시 - 청소
군인 : 21시 - 저녁 점호
군인 : 22시 - 취침
군인답게 매 시간마다 시계를 보고 할 일을 한다.
이번엔 Observer
를 상속하는 학생 클래스를 구현해 보자.
import java.util.Objects;
public class Student implements Observer {
private final String[] todos = new String[25];
public Student() {
todos[ 7] = "기상";
todos[ 8] = "식사";
todos[ 9] = "1교시";
todos[10] = "2교시";
todos[11] = "3교시";
todos[12] = "4교시";
todos[13] = "점심 식사";
todos[14] = "5교시";
todos[15] = "6교시";
todos[16] = "동아리 활동";
todos[18] = "저녁 식사";
todos[19] = "야간 자율 학습 시작";
todos[22] = "야간 자율 학습 종료";
}
@Override
public void update(String message) {
// do nothing
}
@Override
public void update(int message) {
if (Objects.isNull(todos[message]))
return;
System.out.println("학생 : " + message + "시 - " + todos[message]);
}
}
Student
클래스도 한 번 메인 함수에서 불러와 사용해 보자.
import Army;
import Clock;
import Student;
public class Main {
public static void main(String[] args) {
Clock clock = new Clock();
Army army = new Army();
clock.addObserver(army);
Student student = new Student();
clock.addObserver(student);
clock.clock();
}
}
실행 결과
군인 : 1시 - 불침번 기상
군인 : 2시 - 다음 번대 불침번 깨우기
군인 : 7시 - 아침 점호
학생 : 7시 - 기상
군인 : 8시 - 아침 식사
학생 : 8시 - 식사
군인 : 9시 - 오전 일과 시작
학생 : 9시 - 1교시
학생 : 10시 - 2교시
학생 : 11시 - 3교시
군인 : 12시 - 점심 식사
학생 : 12시 - 4교시
군인 : 13시 - 오후 일과 시작
학생 : 13시 - 점심 식사
학생 : 14시 - 5교시
학생 : 15시 - 6교시
학생 : 16시 - 동아리 활동
군인 : 17시 - 체력 단련
군인 : 18시 - 저녁 식사
학생 : 18시 - 저녁 식사
군인 : 19시 - 개인 정비
학생 : 19시 - 야간 자율 학습 시작
군인 : 20시 - 청소
군인 : 21시 - 저녁 점호
군인 : 22시 - 취침
학생 : 22시 - 야간 자율 학습 종료
학생도 군인도 각자 매 시간마다 시계를 보고, 시간에 맞춰서 각자 할 일을 하는 것을 볼 수 있다.
clock.addObserver()
에 Army
클래스의 객체와 Student
클래스의 객체를 형 변환 없이 바로 넣었다. 둘 다 Observer
의 역할을 하는, Observer
를 구현한 클래스였기 때문에 형 변환 등의 작업 없이도 오류 없이 들어갈 수 있었던 것이다.