Computer Science/디자인 패턴

옵저버 패턴(관찰자 패턴, Observer Pattern)에 대해 알아보자

TwinParadox 2021. 9. 14. 08:27
728x90

옵저버 패턴(Observer Pattern)?

어떤 클래스(들)의 동작이 특정 상태 변화에 따라서 동작해야 하는 경우를 생각해보자.

예를 들어서,

 

  • 어떤 구독 서비스를 이용하는 유료 회원과 무료 회원이 존재한다.
  • 구독한 유료 회원에게만 신규 콘텐츠가 등록되면 뉴스 레터 형태의 알림이 전송되어야 한다.

 

이런 상황에서 무료 회원은 반드시 해당 서비스의 페이지에 들어가서 신규 콘텐츠가 존재하는지를 확인해야 한다.

유료회원은 구독 서비스가 전송한 뉴스 레터를 통해서도 확인할 수 있는 추가적인 방법이 생기는 셈이다.

이 때, 구독 서비스가 새로운 내용(콘텐츠)가 등록되었을 때 모든 유저들에게 뉴스 레터를 전송하는 방식을 구성하는데 적용해볼 수 있는 것이 옵저버 패턴이다.

 

여기서 유료 회원은 Observer고, 신규 내용을 제공하는 서비스가 Observable(Subject)으로 적용해보자. 영문 해석 그대로, 유료 회원은 신규 서비스의 상태를 보는 관찰자가 되고, 서비스는 관찰 대상이 된다.

 

 

흉내내보기

Observer UML - Wikipedia

일단 기본적인 Observer의 UML 구조를 살펴보자.

 

  • Observer와 Subject라는 두 객체가 존재한다.
  • Subject는 Observer의 리스트를 보유하고 있고, 이를 추가하거나 삭제할 수 있다.
  • Observer를 구현한 각 Observer들(여기서는 ConcreteObserverA, ConcreteObserverB)는 update()를 구현해야 한다.
  • Subject는 Observer가 추가되거나 삭제되더라도 영향 받지 않는다.
  • Subject는 상태가 변화하면 각 Observer의 update()를 실행한다.

 

Subject 인터페이스

public interface Subject<T> {
	void registerObserver(Observer<T> observer);
	void unregisterObserver(Observer<T> observer);
	void notifyObserver(T event);
}

 

Observer 인터페이스

public interface Observer<T> {
	void observe(T event);
}

 

Observer 구현체

public class ConcreteObjectA implements Observer<String> {
	@Override
	public void observe(String event) {
		System.out.println("ObserverA: " + event);
	}
}
public class ConcreteObjectB implements Observer<String> {
	@Override
	public void observe(String event) {
		System.out.println("ObserverB: " + event);
	}
}

 

Subject 구현체

public class ConcreteSubject implements Subject<String> {
	// Thread-Safe 고려한 세팅, 고려하지 않아도 되면 List<> 사용해도 무방
	private final Set<Observer<String>> observers = new CopyOnWriteArraySet<>();

	@Override
	public void registerObserver(Observer<String> observer) {
		observers.add(observer);
	}

	@Override
	public void unregisterObserver(Observer<String> observer) {
		observers.remove(observer);
	}

	@Override
	public void notifyObserver(String event) {
		observers.forEach(observer -> observer.observe(event));
	}
}

 

사용 예시

public void test() {
	Subject<String> subject = new ConcreteSubject();
	Observer<String> observerA = new ConcreteObjectA();
	Observer<String> observerB = new ConcreteObjectB();

	subject.notifyObserver("New Contents1 Arrived!");

	subject.registerObserver(observerA);
	subject.notifyObserver("New Contents2-1 Arrived!");

	subject.registerObserver(observerB);
	subject.notifyObserver("New Contents2-2 Arrived!");

	subject.unregisterObserver(observerA);
	subject.notifyObserver("New Contents3 Arrived!");
}

 

위와 같은 코드를 동작시켰을 때, Contents 1은 어떤 Observer도 등록되어 있지 않아 출력되지 않는다.

나머지는 Contents는 등록 여부에 따라서 다음과 같이 출력된다.

 

 

ObserverA: New Contents2-1 Arrived!

ObserverA: New Contents2-2 Arrived!

ObserverB: New Contents2-2 Arrived!

ObserverB: New Contents3 Arrived!

 

 

이렇게 간단하게 옵저버 패턴을 구현해봤다. 자바에서는 이렇게 손수 Observer와 Subject 인터페이스를 만들지 않고, java.util.Observer 인터페이스와 java.util.Observable 클래스를 통해 활용이 가능하다. 다만, 해당 API는 Deprecated 되어서 더 이상 사용하는 것을 권장하지 않는다. [참고문서]

 

  • Observer는 인터페이스고 Observable은 클래스면서 발생하는 문제
  • 상속 위주의 작업으로 진행하는 문제
  • java.util에 들어 있어서 발생하는 문제
  • 기타 등등...

 

이러한 연유로, 더 이상 이쪽 패키지의 클래스와 인터페이스를 사용하지 않는게 좋겠다.

java.beans에 있는 PropertyChangeListener, PropertyChangeEvent를 사용하는 것을 권장하고 있다.

이걸 사용하면 대략적으로 이런 느낌으로 사용할 수 있다.

 

public class NewConcreteObserver implements PropertyChangeListener {
	private String message;

	@Override
	public void propertyChange(PropertyChangeEvent evt) {
		this.setMessage((String) evt.getNewValue());
	}

	public void setMessage(String message) {
		this.message = message;
	}

	public String getMessage() {
		return message;
	}
}

public class NewConcreteSubject {
	private String message;
	private PropertyChangeSupport support;

	public NewConcreteSubject() {
		support = new PropertyChangeSupport(this);
	}

	public void addPropertyChangeListener(PropertyChangeListener pcl) {
		support.addPropertyChangeListener(pcl);
	}

	public void removePropertyChangeListener(PropertyChangeListener pcl) {
		support.removePropertyChangeListener(pcl);
	}

	public void setMessage(String value) {
		support.firePropertyChange("message", this.message, value);
		this.message = value;
	}
}
NewConcreteObserver observer = new NewConcreteObserver();
NewConcreteSubject observable = new NewConcreteSubject();

observable.addPropertyChangeListener(observer);
observable.setMessage("message");

 

 

마무리하며...

이걸 왜 사용해야 하는지에 대해서 생각해보면,

 

  • 실시간으로 객체 변경 사항을 다른 객체에 전파가 가능하다.
  • 느슨한 결합으로 시스템의 유연성을 가져갈 수 있다.
  • Subject나 Observer의 변경 없이도 다른 개체로 데이터를 효과적으로 전송할 수 있다.

 

한편으로, 이 옵저버 패턴을 너무 남용하듯 적용했을 때의 부작용을 생각해보자.

 

  • 너무 많으면 오히려 어떤 상태가 어떻게 전파되는지 파악하기 어렵다.
  • 데이터를 어느 선에서 배분하느냐도 문제가 된다.

 

 

 

Reference

https://docs.oracle.com/javase/8/docs/api/java/util/Observable.html

https://stackoverflow.com/questions/46380073/observer-is-deprecated-in-java-9-what-should-we-use-instead-of-it

https://www.baeldung.com/java-observer-pattern

728x90
728x90