옵저버 패턴

Hyeokwoo Kwon·2022년 2월 17일
0

옵저버 패턴이란

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);
}

SubjectObserver를 인터페이스로 만들었다. 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를 구현한 클래스였기 때문에 형 변환 등의 작업 없이도 오류 없이 들어갈 수 있었던 것이다.
  • jdk에서 Observable 클래스와 Observer 인터페이스를 제공하므로, 직접 구현할 필요 없이 간편하게 사용할 수 있다.

0개의 댓글