Observer

GamSa Ham·2022년 11월 10일
0

GoF디자인패턴

목록 보기
14/22

의도

객체 사이에 일 대 다의 의존 관계를 정의해 두어, 어떤 객체의 상태가 변할 때 그 객체에 의존성을 가진 다른 객체들이 그변화를 통지받고 자동으로 갱신될 수 있게 만듭니다.

다른 이름

종속자(Dependent), 게시-구독(Publish-Subscribe)

동기

객체 간에 일관성을 유지하도록 해야 합니다. 일관성 관리를 위해서 객체 간의 결합도를 높이고 싶지는 않습니다.

예를들어, 그래픽 사용자 인터페이스 툴킷은 표현 부분과 이에 대응하는 데이터를 분리합니다. 응용프로그램 자료와 표현을 정의하는 클래스는 독립적으로 재사용 할 수 있습니다. 그러나 이들은 함께 동작해야합니다.

아래는 그림으로 예시

감시자 패턴은 주체(Subject)와 감시자(observer)입니다. 주체는 여러게의 감시가 있을 수있습니다. 각 감시자는 주체의 상태와 자신의 상태를 동기화시키기 위해 주체의 상태를 알아봅니다.

이런 종류의 상호작용을 게시 - 구독 관계라고 합니다. 주체는 상태 변경에 대한 통보를 하는 것이므로 누가 감시자인지 모른 채 통보를 발송합니다. 불특정 다수의 감시자가 이 통보를 수신하기 위해 구독을 신청하는 것입니다.

활용성

다음 상황 중 어느 한 가지에 속하면 감시자 패턴을 사용합니다.

  • 어떤 추상 개념이 두 가지 양상을 갖고 하나가 다른 하나에 종속적일 때, 각 양상을 별도의 객체로 캡슐화하여 이들 각가을 재사용 할 수 있습니다.
  • 한 객체에 가해진 변경으로 다른 객체를 변경해야 하고, 프로그래머들은 얼마나 많은 객체들이 변경되야 하는지 몰라도 될때
  • 어떤 객체가 다른 객체에 자신의 변화를 통보할 수 있는데, 그 변화에 관심 있어 하는 객체들이 누구인지에 대한 가정 없이도 그러한 통보가 될 때

구조

참여자

  • Subject: 감시자들이 알고 있는 주체입니다. 임의 개수의 감시자 객체는 주체를 감시할 수 있습니다. 주체는 감시자 객체를 붙이거나 떼는 데 필요한 인터페이스를 제공합니다.
  • Observer: 주체에 생긴 변화에 관심 있는 객체를 갱신하는 데 필요한 인터페이스를 정의합니다. 이로써 주체의 변경에 따라 변화되어야 하는 객체들의 일관성을 유지합니다.
  • ConcretSubject: ConcretObserver 객체에게 알려주어야 하는 상태를 저장합니다. 또한 이 상태가 변경 될 때 감시자에게 변경을 통보합니다.
  • ConcretObserver: ConcretSubject 객체에 대한 참조자를 관리합니다. 주체 상태와 일관성을 유지해야 하는 상태를 저장합니다. 주체의 상태와 감시자의 상태를 일관되게 유지하는 데 사용하는 갱신 인터페이스를 구현합니다.

협력 방법

  • ConcreteSubject는 Observer의 상태와 자신의 상태가 달라지는 변경이 발생할 때마다 감시자에게 통보합니다.
  • concrete subject 에서 변경이 통보된 후 ConcreteObserver는 필요한 정보를 주체에게 질의하여 얻어 옵니다. ConcreteObserver는 이 정보를 이용해서 주체의 상태와 자신의 상태를 일치시킵니다. 다음 다이어그램은 하나의 주체 및 두 감시자 사이의 협력 관계를 표현한 것입니다.

변경을 유발하는 Observer 객체가 어떻게 주체한테서 변경 통보를 받을 때까지 수정을 늦추는지 알아봅시다.

Notify() 연산은 주체로만 호출되는 것이 아니고, 감시자나 다른 객체들로도 호출이 가능합니다.

결과

감시자 패턴을 사용하게 되면 주체 및 감시자 모두를 독립적으로 변형하기 쉽습니다. 감시자를 재사용하지 않고도 주체를 재사용 할 수 있고, 주체 없이도 감시자를 재사용 할 수 있습니다. 또한 주체나 감시자의 수정 없이도 감시자를 추가할 수 있습니다.

감시자 패턴을 사용함으로써 얻을 수 있는 그 밖의 이익은 다음과 같습니다.

  1. Subject와 Observer 클래스 간에는 추상적인 결합도만이 존재합니다.
  2. 브로드캐스트(broadcast) 방식의 교류를 가능하게 합니다.
  3. 예측하지 못한 정보를 갱신합니다.

구현

  1. 주체와 그것의 감시자를 대응시킵니다.
  2. 하나 이상의 주체를 감시합니다.
  3. 누가 갱신을 촉발(trigger)시킬 것인가?
  4. 삭제한 주체에 대한 무효 참조자를 계속 유지할 때가 있습니다.
  5. 통보 전에 주체의 상태가 자체 일관성을 갖추도록 만들어야 합니다.

예제코드

package study.designpattern;

import java.util.ArrayList;
import java.util.List;

class Subject {

    public void attach(Observer observer) {
        observers.add(observer);
    }
    public void detach(Observer observer) {
        observers.remove(observer);
    }
    public void myNotify() {

        for (Observer observer : observers) {
            observer.update(this);
        }

    }

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

}

interface Observer {
    void update(Subject theChangedSubject);
}

class ClockTimer extends Subject {

    public int getHour() {
        return 0;
    }

    public int getMinute() {
        return 0;
    }

    public int getSecond() {
        return 0;
    }

    void trick() {
        notify();
    }
}

interface Widget {
    void draw();
}

class DigitalClock implements  Widget, Observer {

    public DigitalClock(ClockTimer subject) {
        this.subject = subject;
        this.subject.attach(this);
    }

    // 1. 자바에는 소멸자 개념이 없고 가비지 켤랙션에서 삭제될때 발생하는 메시지라 추후 재대로 동작하는지 확인 필요
    // 2. Effect 자바 항목에서는 finalize 를 사용하지말라고 권고함
    @Override
    protected void finalize() throws Throwable {
        this.subject.detach(this);
        super.finalize();
    }

    @Override
    public void update(Subject theChangedSubject) {

        if(theChangedSubject == this.subject) {
            draw();
        }

    }

    @Override
    public void draw() {
        int hour = subject.getHour();
        int minute = subject.getMinute();

        System.out.println("DigitalClock.draw");
        // 등 디지털 시간을 표기합니다.
    }

    private ClockTimer subject;
}

// 확장됩니다.
class AnalogClock implements Widget, Observer {

    public AnalogClock(ClockTimer subject) {
        this.subject = subject;
        this.subject.attach(this);
    }

    // 1. 자바에는 소멸자 개념이 없고 가비지 켤랙션에서 삭제될때 발생하는 메시지라 추후 재대로 동작하는지 확인 필요
    // 2. Effect 자바 항목에서는 finalize 를 사용하지말라고 권고함
    @Override
    protected void finalize() throws Throwable {
        this.subject.detach(this);
        super.finalize();
    }

    @Override
    public void update(Subject theChangedSubject) {
        // ...
    }

    @Override
    public void draw() {
        System.out.println("AnalogClock.draw");
        // ...
    }

    private ClockTimer subject;
}

public class ObserverPattern {
    public static void main(String[] args) {

        ClockTimer timer = new ClockTimer();
        AnalogClock analogClock = new AnalogClock(timer);
        DigitalClock digitalClock = new DigitalClock(timer);

        timer.myNotify();
        // timer의 시간이 바뀔 때마다 두 개의 시계가 수정되어 다시 출력됩니다.
    }
}

관련 패턴

복잡한 갱신의 의미 구조를 캡슐화함으로써, ChageManager 객체는 주체와 감시자 사이의 중재자 역할을 합니다. ChangeManager 객체는 단일체패턴을 써서 시스템에 하나만 존재하고 전역적으로 접근이 가능하도록 만들 수 있습니다.

profile
안녕하세요. 자바를 좋아하고 디자인 패턴, Refactoring, Clean Code에 관심이 많은 백엔드 개발자입니다.

0개의 댓글