코드 디자인 패턴/java

[java & 디자인 패턴] Observer 패턴 학습 정리

tea-tea 2024. 7. 14. 06:50

개념


한 객체의 상태 변경 시, 의존하는 다른 객체에게 자동으로 갱신 이벤트가 발생할 수 있도록 하는 패턴이다.

감시당하는 대상과 감시하는 객체 간의 관계로 일대다 의존성 관계이다.

 

  • 사용하는 경우
    • view나 button 등 위젯에서 이벤트를 받을 때, 구독 기능
    • 안드로이드에서 onClickListener 이벤트는 버튼 같은 ui는 감시당하는 대상(obserable), 이벤트는 감시 대상이 업데이트될 때 반응하는 대상(observer)

 

구현


java.util의 클래스 및 인터페이스 상속하기

옵저버 패턴을 위해서 자바는 기본적으로 observable 객체와 observer 인터페이스를 제공한다.

이를 각각 감시당하는 대상과 의존성 객체(감시하는 객체)에게 상속 및 구현한다.

주의사항
observer 인터페이스와 observable 클래스는 자바 8버전까지만 사용 가능하다.

 

예시

아래 예시는 observable의 상속 객체인 Subject와 observer의 구현 객체인 DependedToSubject1를 생성하고 서로 연결한다. 그리고 Subject의 상태를 변경하면 Subject의 메소드인 setChange와 notifyObserver가 실행된 후, 이에 반응해 DependedToSubject1의 update 메소드가 실행되며 의존성 작업을 실행한다.

import java.util.Observable;
import java.util.Observer;

public class Subject extends Observable {
	private boolean flag;

	public Subject() {
		// TODO Auto-generated constructor stub
	}

	public boolean isFlag() {
		return flag;
	}

	public void setFlag(boolean flag) {
		this.flag = flag;

		setChanged();

		notifyObservers();
	}

}

public class DependedToSubject1 implements Observer {

	private Observable observable;

	private boolean flag;

	public DependedToSubject1(Observable arg) {
		this.observable = arg;
		arg.addObserver(this);
		// TODO Auto-generated constructor stub
	}

	@Override
	public void update(Observable o, Object arg) {
		// TODO Auto-generated method stub
		if (o instanceof Subject) {
			Subject subject = (Subject) o;
			this.flag = subject.isFlag();
			depAction();
		}

	}

	private void depAction() {
		// TODO Auto-generated method stub

		System.out.println("의존 객체 상태 변경: flag: " + flag);

	}

}

public class Client {

	public static void main(String[] args) {
		// TODO Auto-generated method stub

		
		Subject subject = new Subject();
		DependedToSubject1 dependedToSubject1 = new DependedToSubject1(subject);
		
		
		
		System.out.println("subject 객체의 상태 변경");
		subject.setFlag(false);
		
	}

}

 

한계

이 방법은 기존 구현된 클래스 및 인터페이스를 이용하므로 빠르게 구현가능하지만 다음 단점이 있다.

  • 두 객체는 자바 8버전 이후로 지원하지 않는다.
  • observable은 클래스이므로, 이미 다른 클래스를 상속받고 있으면 사용 불가
  • observable의 핵심 메소드인 setChanged는 외부에서 호출할 수 없다.

 

oberver, obserable 인터페이스 직접 구현하기

위에서 다룬 문제를 해결하기 위해 observer, obserable 기능을 직접 구현할 수 있다.

 

public interface IObserverDependedToSubject1 {
	public void update(Subject o);
}

public class DependedToSubject1 implements IObserverDependedToSubject1 {
	private boolean flag;

	@Override
	public void update(Subject o) {
		// TODO Auto-generated method stub

		this.flag = ((Subject) o).isFlag();
		depAction();

	}

	private void depAction() {
		// TODO Auto-generated method stub

		System.out.println("의존 객체 상태 변경: flag: " + flag);

	}

}

public interface IObservableSubject {
	public void addObserver(DependedToSubject1 o);

	public void deleteObserver(DependedToSubject1 o);

	public void notifyObserver();

}

public class Subject implements IObservableSubject {
	private List< DependedToSubject1> deMap =new ArrayList<>();
	private boolean flag;

	public Subject() {
		// TODO Auto-generated constructor stub
	}

	public boolean isFlag() {
		return flag;
	}

	public void setFlag(boolean flag) {
		this.flag = flag;
		this.notifyObserver();
	}

	@Override
	public void addObserver(DependedToSubject1 o) {
		// TODO Auto-generated method stub
		deMap.add(o);
		
	}

	@Override
	public void deleteObserver(DependedToSubject1 o) {
		// TODO Auto-generated method stub
		deMap.remove(o);
	}

	@Override
	public void notifyObserver() {
		// TODO Auto-generated method stub
		
		for(DependedToSubject1 e : deMap) {
			e.update(this);
		}
		
	}

}

public class Client {

	public static void main(String[] args) {
		// TODO Auto-generated method stub

		
		Subject subject = new Subject();
		DependedToSubject1 dependedToSubject1 = new DependedToSubject1();
		
		subject.addObserver(dependedToSubject1);
		
		System.out.println("subject 객체의 상태 변경");
		subject.setFlag(false);
		
	}

}

 

참고. ConcurrentModificationException 에러
자바와 자바스크립트는 공통점으로 index키 기반의 객체 자료형이 존재한다.
자바스크립트에선 array가 있고, 자바애서는 collection이 있다.

둘다 for e in arr, 혹은 for e: arr처럼 배열 반복문 방식이 가능한데, 작동방식에서 차이가 있다.

자바스크립트는 배열 반복문에서 원본을 수정해도 에러가 발생하지 않으며 수정된 상태를 반복문에 반영한다.
반면, 자바는 배열 반복문에서 원본을 수정하면 ConcurrentModificationException 에러가 발생한다.

이는 자바 컬렉션 프레임워크의 fail-fast 메커니즘 때문이다.
collection은 배열 반복문 시, Iterator를 생성하는데, 원본 배열이 수정되면 다음 반복에서 next()를 호출하며 이 부분에서 ConcurrentModificationException이 발생한다.
이런 동작방식은 자바는 멀티스레드 구조이다 보니, 컬렉션에 동시에 접근해서 수정하는 경우가 많으므로 예기치 않은 문제를 막기 위해서다.