본문 바로가기

디자인패턴

Observer Pattern

Observer Pattern 이란?

  • 옵저버 패턴에서는 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들한테 연락이 가고 자동으로 내용이 갱신되는 방식으로 일대다 의존성을 정의합니다.
  • 옵저버 패턴의 의도는 객체 사이에 일 대 다의 의존 관계를 정의해 두어, 어떤 객체의 상태가 변할 때 그 객체에 의존성을 가진 다른 객체들이 그 변화를 통지받고 자동으로 갱신될 수 있게 만드는 것입니다.

일대다 관계

  • 옵저버 패턴에서 상태를 저장하고 지배하는 것은 Subject 객체입니다. 따라서 상태가 들어있는 객체는 하나만 있을 수 있습니다. 하지만 Observer는 반드시 상태를 갖고 있어야 하는 것은 아닙니다. 따라서 Observer는 여러 개가 있을 수 있으며, 상태가 바뀌었다는 것을 알려주는 Subject에 의존적인 성질을 가지게 됩니다.

 

클래스 다이어그램

옵저버 패턴 Class Diagram

 

코드 예시

public interface Subject {
    void registerObserver(Observer o);
    void removeObserver(Observer o);
    void notifyObservers();
}
  • registerObserver : 옵저버를 등록합니다.
  • removeObserver : 옵저버를 제거합니다.
  • notifyObservers : 옵저버에게 정보를 전달합니다.

 

public class ConcreteSubject implements Subject {

    private List<Observer> observerList;
    private float data1;
    private float data2;
    private float data3;

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

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

    @Override
    public void removeObserver(Observer o) {
        observerList.remove(o);
    }

    @Override
    public void notifyObservers() {
        for (Observer observer : observerList) {
            observer.update(data1, data2, data3);
        }
    }

    public void dataChanged() {
        notifyObservers();
    }

    public void setData(float data1, float data2, float data3) {
        this.data1 = data1;
        this.data2 = data2;
        this.data3 = data3;
        dataChanged();
    }
}
  • ConcreteObserver에게 알려줘야 하는 상태를 저장하고 관리합니다.
  • 자신의 상태가 달라지면 ConcreteObserver에게 알려줍니다.

 

public interface Observer {
    void update(float data1, float data2, float data3);
}
  • Observer 는 Subject의 상태 변화에 관심을 갖습니다.

 

public class ConcreteObserver implements Observer {

    private float data1;
    private float data2;
    private float data3;
    private Subject subject;

    public ConcreteObserver(Subject subject) {
        this.subject = subject;
        subject.registerObserver(this);
    }

    @Override
    public void update(float data1, float data2, float data3) {
        this.data1 = data1;
        this.data2 = data2;
        this.data3 = data3;
        // update된 상태를 바탕으로 다른 작업을 수행
    }
}
  • Observer 객체들은 Subject 객체에 등록되어 있으며 Subject의 데이터가 바뀌면 갱신 내용을 전달 받습니다

 

위와 같이 코드로 옵저버 패턴에 대해 알아보았는데요.

실제 옵저버 패턴에는 푸시(Push) 방식과 풀(Pull) 방식 2가지가 존재합니다. 위의 방식은 푸시 방식인데, 푸시 방식은 데이터를 Subject가 Observer에게 직접 전달하는 방식입니다. 푸시 방식의 장점은 데이터의 제어를 Subject 객체만이 하기 때문에 옵저버에게 자기 자신을 많이 드러내지 않아도 된다는 것입니다.

 

그럼 풀(Pull) 방식에 대해서도 알아보겠습니다.

풀 방식은 Subject 객체로부터 변경 알림을 받고 Observer 객체에서 변경된 데이터를 직접 가져가는 방식입니다. Observer가 관찰하는 데이터가 많거나 Observer마다 관찰하는 데이터가 다를 때 사용하는 방식입니다.

public class ConcreteObserver implements Observer
     
     public void update(Subject subject) {
        if (subject instanceof ConcreteSubject) {
            ConcreteSubject concreteSubject = (ConcreteSubject) subject;
            // 알림을 받았을 때, get을 할지 말지, 어떤 데이터를 get 해올지 로직 추가 가능
            concreteSubject.getData();
            // 작업 진행
        }
    }
}
  •  풀 방식의 장점은 옵저버 객체에서 원하는 데이터가 각기 다를 때, 개별적으로 데이터를 가져올 수 있습니다.
    상황에 따라 푸시 방식, 풀 방식을 구분해서 사용하면 되겠습니다.

 

++ 느슨한 결합의 위력

  • 두 객체가 느슨하게 결합되어 있다는 것은, 그 둘이 상호작용을 하긴 하지만 서로에 대해 서로 잘 모른다는 것을 의미합니다.
  • Subject가 Observer에 대해서 아는 것은 Observer가 특정 인터페이스(여기선 Observer)를 구현한다는 것 뿐입니다. 옵저버의 Concrete 클래스가 무엇인지, 무엇을 하는지 등에 대해서 알 필요가 없습니다.
  • 옵저버는 언제든지 새로 추가할 수 있습니다. 실행 중에 한 옵저버를 다른 옵저버로 바꿔도 되고, 아무때나 제거해도 됩니다.
  • 새로운 형식의 옵저버를 추가하려고 할 때도 Subject를 전혀 변경할 필요가 없습니다.
  • Subject와 Observer는 서로 독립적으로 재사용할 수 있습니다.
  • Subject Observer가 바뀌더라도 서로한테 영향을 끼치지 않습니다.

 

참고

Head First Design Patterns / 에릭 프리먼 등 저 / 서환수 역 / 한빛미디어

'디자인패턴' 카테고리의 다른 글

Decorator Pattern  (0) 2021.08.07
디자인 패턴 23가지  (0) 2021.08.05