디자인 패턴(구)/행위 패턴

감시자(Observer) 패턴이란?

공대키메라 2022. 4. 24. 21:30

1. 감시자 패턴이란?

의도

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

 

즉, 다수의 객체가 특정 객체 상태 변화를 감지하고 알림을 받는 패턴이다.

 

이것을 이용해 우리는 발행(publish)-구독(subscribe) 패턴을 구현할 있다.

사용 시기

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

구조

혹은 다음과 같은 구조를 가진다.

출처 : https://dejavuhyo.github.io/posts/observer-pattern/

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

구조는 조금씩 다를지라도 의도에 집중을 하면 결국 같은 것이다. 

 

다른 객체들이 변화를 통지받고 자동으로 갱신될 수 있게 만들면 되는것 아닌가?

 

장점

  • 상태를 변경하는 객체(publisher)와 변경을 감지하는 객체(subsriber)의 관계를 느슨하게 유지할 수 있다.
  • Subject상태 변경을 주기적으로 조회하지 않고 자동으로 감지할 있다.
  • 런타임에 옵저버를 추가하거나 제거할 있다.

단점

  • 복잡도가증가한다.
  • 다수의 Observer 객체를등록이후해지않는다면 memory leak이발생할수도있다

2. 구현

다음으로 작성하는 코드들은 다음에 오는 사이트에서 가져왔다. 클래스명들은 필자가 이해를 더 쉽게 하기 위해 구조에서 정해준 이름과 일치시켰다.

출처 영상과 코드 참고 사이트를 순서대로 첨부했다.

 

이 영상과 예시에서는 주식 시장을 예로 들었다.

 

주식시장에 등록이 되면 모든 주식 중개인(Observer)이 가격 변동 사항에 대해 알도록 작성한 코드라고 이해하면 좋을 것 같다.

 

출처 :

https://www.youtube.com/watch?v=vNHpsC5ng_E&t=122s 

https://www.newthinktank.com/2012/08/observer-design-pattern-tutorial/

Subject.java

public interface Subject {
    void add(Observer observer);
    void remove(Observer observer);
    void notifyObserver();
}

Observer.java

public interface Observer {
    public void update(double ibmPrice, double aaplPrice, double googPrice);
}
 

ConcreteObserver.java

public class ConcreteObserver implements Observer{

    private double ibmPrice;
    private double aaplPrice;
    private double googPrice;

    private static int observerIDTracker = 0;

    private int observerID;

    private Subject subject;

    public ConcreteObserver(Subject subject) {
        this.subject = subject;
        this.observerID = ++observerIDTracker;
        System.out.println("New Observer " + this.observerID);
        subject.add(this);
    }

    @Override
    public void update(double ibmPrice, double aaplPrice, double googPrice) {
        this.ibmPrice = ibmPrice;
        this.aaplPrice = aaplPrice;
        this.googPrice = googPrice;
        printThePrices();
    }

    public void printThePrices() {
        System.out.println("\n" + observerID + "\nIBM : " + ibmPrice
        + "\nAPPL : " + aaplPrice + "\nGOOD : " + googPrice);
    }

}

 ConcreteSubject.java

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

public class ConcreteSubject implements Subject{

    private List<Observer> observerList;
    private double ibmPrice;
    private double aaplPrice;
    private double googPrice;

    public ConcreteSubject() {
        this.observerList = new ArrayList<>();
    }

    @Override
    public void add(Observer newObserver) {
        observerList.add(newObserver);
    }

    @Override
    public void remove(Observer deleteObserver) {
        int observerIdx = observerList.indexOf(deleteObserver);
        System.out.println("Observer index : " + (observerIdx+1) + " is deleted");
        observerList.remove(observerIdx);
    }

    @Override
    public void notifyObserver() {
        for (Observer observer : observerList) {
            observer.update(ibmPrice, aaplPrice, googPrice);
        }
    }

    public void setIbmPrice(double newIbmPrice) {
        this.ibmPrice = newIbmPrice;
        notifyObserver();
    }

    public void setAaplPrice(double newAaplPrice) {
        this.aaplPrice = newAaplPrice;
        notifyObserver();
    }

    public void setGoogPrice(double newGoogPrice) {
        this.googPrice = newGoogPrice;
        notifyObserver();
    }


}

Main.java

public class Main {

    public static void main(String[] args) {
        ConcreteSubject subject = new ConcreteSubject();
        ConcreteObserver observer = new ConcreteObserver(subject);

        subject.setIbmPrice(127.000);
        subject.setAaplPrice(222.000);
        subject.setGoogPrice(333.000);

        ConcreteObserver observer2 = new ConcreteObserver(subject);
        
        subject.setIbmPrice(444.00);
        subject.setAaplPrice(555.60);
        subject.setGoogPrice(666.40);
        
        subject.remove(observer);
        
        subject.setIbmPrice(197.00);
        subject.setAaplPrice(677.60);
        subject.setGoogPrice(676.40);

    }

}

 

출력 결과 확인

더보기
New Observer 1

1
IBM : 127.0
APPL : 0.0
GOOD : 0.0

1
IBM : 127.0
APPL : 222.0
GOOD : 0.0

1
IBM : 127.0
APPL : 222.0
GOOD : 333.0

New Observer 2

1
IBM : 444.0
APPL : 222.0
GOOD : 333.0

2
IBM : 444.0
APPL : 222.0
GOOD : 333.0

1
IBM : 444.0
APPL : 555.6
GOOD : 333.0

2
IBM : 444.0
APPL : 555.6
GOOD : 333.0

1
IBM : 444.0
APPL : 555.6
GOOD : 666.4

2
IBM : 444.0
APPL : 555.6
GOOD : 666.4
Observer index : 1 is deleted

2
IBM : 197.0
APPL : 555.6
GOOD : 666.4

2
IBM : 197.0
APPL : 677.6
GOOD : 666.4

2
IBM : 197.0
APPL : 677.6
GOOD : 676.4
Observer index : 1 is deleted

Process finished with exit code 0

 

물론 코드는 필자가 다른 곳에서 참고(훔쳐) 했지만, 중요한 것은 이 코드의 구성이 우리가 생각하는 감시자 패턴에 부합하는지이다.

 

다시 구조를 봐보자.

출처 : https://dejavuhyo.github.io/posts/observer-pattern/

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

우리가 구현한 ConcreteSubject는 Subject의 구현체이다. Subject는 interface로 해도, abstract로 해도 무방하다. 

 

필자는 이것을 단지 interface로 구현했을 뿐이다. 

 

그리고 우리는 Observer를 통해 update된 사실을 모든 observer에 전달한다. 

 

우리의 의도에 맞도록 등록된 감시자(Observer)들이 update를 통해서 전부 내용을 전달받는다. 

 

Subject와 Observer는 합성 관계로, 부분이 전체에 종속적이고 라이프 사이클을 관리한다 라고 볼 수 있습니다.

즉, 부분 객체가 전체 객체에 속하는 관계이다. Observer는 Subject의 전체에 속하는 것이다. 


또 흥미로운 점은 ConcreteObserver에서 ConcreteSubject와 연관관계를 맺을 수도 있다는 점이다.

 

이상해 보이지만 이것은 영리한거라고 한다. 

 

이렇게 한 이유는, ConcreteObserver를 인스턴스화 할 때 ConcreteSubject를 생성자 파라미터로 넘겨주게 된다면, 

ConcreteObserver 에서 ConcreteSubject에 접근이 가능하다는 것이다. 

 

만일 이렇게 해주지 않으면, ConcreteSubject에서 수정이 일어나면 ConcreteObserver에서는 이것을 update해서 정보를 받아야 하는데 그럴 방법이 없다는 것이다. 

3. 또 다른 참고 예

출처 : https://www.youtube.com/watch?v=_BpmfnqjgzQ&t=2562s 

 

위에 소개된 영상에서는 이 전반적인 구조에 대해 굉장히 자세하게 설명해주며, Weather Station을 예로 들었다.

 

 

Weather Station이 있는데, 이곳에서 수집한 날짜 정보를 모든 기기에(핸드폰, 티비, window 등등...)의 화면에 보여주는 경우에도 동일한 패턴을 기대할 수 있다.


이번에는 Observer 패턴에 대해 알아보았다. 

 

이상한 점이 있다면 언제든 피드백은 환영합니다. 꾸벅스

'디자인 패턴(구) > 행위 패턴' 카테고리의 다른 글

전략(Strategy) 패턴이란?  (0) 2022.04.27
상태(state) 패턴이란?  (0) 2022.04.26
메멘토(Memento) 패턴이란?  (0) 2022.04.23
중재자(Mediator) 패턴이란?  (0) 2022.04.23
반복자(Iterator) 패턴이란?  (0) 2022.04.21