옵저버 패턴의 정의

  • 옵저버 패턴(Observer Pattern)에서는 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들한테 연락이 가고 자동으로 내용이 갱신되는 방식으로 일대다(one-to-many)의존성을 정의 한다

  • 신문 구독 메커니즘에서 출판사를 주제(subject), 구독자를 옵저버(observer)라고 생각하시면 이해하기 쉽다.


  • 옵저버 패턴을 적용하여 어플리케이션을 만들어 보겠다.
  1. 우리가 만들 애플리케이션은 위치 어플리케이션이다.
  2. Position 객체를 받아서 x좌표 y좌표를 받아서 디스플레이 장비에서 갱신해 가면서 보여주는 어플리케이션이다.
  • Position 인터페이스
public classPosition {
	public float getPositionX() {
		return 0.0f;
	}

	public float getPositionY() {
		return 0.0f;
	}
        /*
        * 포지션 값이 변경될 때마다 알려주기 위한 메서드
        */
	public void positionChanged(){
		float positionX = getPositionX();
		float positionY = getPositionY();
	}
}
  • 지금 알고 있는 것들
  1. x좌표 y좌표를 알아낼 수 있는 메서드가 있다.
  2. 새로운 좌표 데이터가 나올 때마다 positionChanged 메소드가 호출된다.
  3. 새로운 좌표 데이터가 나올 때마다 디스플레이를 갱신해야 한다.
  4. 시스템이 확장 가능해야 한다. 다른 개발자들이 별도의 디스플레이 항목을 만들 수 있도록 해야 하고 사용자들이 애플리케이션에 마음대로 디스플레이 항목을 추가/제거 할 수 있도록 해야 한다.
  • 위의 어플리케이션에 옵저버 패턴을 적용하기 전 옵저버 패턴을 좀더 자세히 알아보자
  1. 옵저버 패턴은 신문 구독메커니즘과 비슷하다
  2. 독자가 특정 신문을 구독 신청하면 새로운 신문이 나올 때마다 배달을 받을 수 있다.
  3. 신문을 더 이상 보고 싶지 않으면 구독 해지 신청을 하면 된다.
  4. 신문사가 계속 운영하는 이상 개인, 기관, 회사 등에서 꾸준히 구독 및 해지를 한다.
  • 이것을 옵저버 패턴과 비교를 하면
  1. 신문사는 주제객체가 된다. 그리고 구독자들은 옵저버 객체가 되는 것이다.
  2. A라는 객체가 옵저버가 되고 싶어한다. 그러면 주제 객체에 옵저버가 되고 싶다고 요청을 한다.
  3. 이렇게 옵저버 객체들은 주제객체에 옵저버로써 등록을 하게 되며 주제객체의 값이 변경되면 등록된 모든 옵저버들은 주제객체에 값이 바뀌었다는 연락을 받습니다. 1.등록된 B라는 객체가 옵저버에서 탈퇴하고 싶어한다. 그러면 B라는 객체는 옵저버 집합에서 제외된다.

  • 여기서 옵저버 패턴을 다시 한번 정의해 보자
  • 옵저버 패턴에서는 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들한테 연락이 가고 자동으로 내용이 갱신되는 방식으로 일대다 의존성을 정의한다.
  • 여기서 중요한 것은 일대 다 관계를 정의한다는 것과 한 객체의 상태가 변경되면 그 객체에 의존하는 모든 객체에 연락이 가는 것이다.

  • 옵저버 객체의 느슨한 결합의 위력
  • 옵저버 패턴에서는 주제와 옵저버가 느슨하게 결합되어 있는 객체 디자인을 제공한다.
  1. 주제가 옵저버에 대해서 아는 것은 옵저버가 특정 인터페이스를 구현한다는 것 뿐이다.
  2. 옵저버는 언제든지 새로 추가 할 수 있다.
  3. 새로운 형식의 옵저버를 추가하려고 할 때도 주제를 전혀 변경할 필요가 없다.
  4. 주제와 옵저버는 서로 독립적으로 재사용 될 수 있다.
  5. 주제나 옵저버가 바뀌더라도 서로한테 영향을 미치지는 않는다.

예제

public interface Subject {
	public void addObserver(Observer o);
	public void removeObserver(Observer o);
	public void notiObserver();
}
public interface Observer {
	public void update(float positionX, float positionY);
}
public interface displayPosition {
	public void display();
}
import java.util.ArrayList;

public class Position implements Subject{
	private ArrayList<Observer> observers;
	private float positionX;
	private float positionY;

	public Position() {
		observers = new ArrayList<>();
	}

	@Override
	public void addObserver(Observer o) {
		observers.add(o);
	}

	@Override
	public void removeObserver(Observer o) {
		int i = observers.indexOf(o);
		if (i >= 0) {
			observers.remove(i);
		}
	}

	@Override
	public void notiObserver() {
		for (int i = 0; i < observers.size(); i++) {
			Observer observer = (Observer) observers.get(i);
			observer.update(positionX, positionX);
		}

	}

	public float getPositionX() {
		return 0.0f;
	}

	public float getPositionY() {
		return 0.0f;
	}
	public void positionChanged(){
		notiObserver();
	}

	public void setPosition(float positionX, float positionY){
		this.positionX = positionX;
		this.positionY = positionY;
		positionChanged();
	}

	//기타메서드
}
public class CurrentDisplay implements Observer, DisplayPosition{
	private float positionX;
	private float positionY;
	private Subject positionData;

	public CurrentDisplay(Subject positionData) {
		this.positionData = positionData;
		positionData.addObserver(this);
	}
	@Override
	public void display() {
		System.out.println("positionX : " + positionX + " positionY :" + positionY);
	}

	@Override
	public void update(float positionX, float positionY) {
		this.positionX = positionX;
		this.positionY = positionY;
		display();
	}
}

  • 자바 내장 옵저버 패턴 사용하기
  • 가장 일반적으로 쓸 수 있는 것은 java.util.Observer 와 java.util.Observalbe

  • 자바 내장 옵저버 패턴 작동 방식
  1. 객체가 옵저버가 되는 방식 : 위에서 구현 한 것과 마찬가지로 java.util.Observer 인터페이스를 구현하고 addObserver()를 호출
  2. Observable에서 연락을 돌리는 방법 : java.util.Observable 클래스를 확장하여 Observable 클래스를 생성 한 후 setChanged() 메소드를 호출해서 객체의 상태가 바뀌었다는 것을 알리고 notifyObservers()를 호출
  3. 옵저버가 연락을 받는 방법 : update()메소드를 구현

예제

package com.kws.javaobserver;

public interface DisplayPosition {
	public void display();
}

package com.kws.javaobserver;
import java.util.Observable;

public class Position extends Observable{
	private float positionX;
	private float positionY;

	public float getPositionX() {
		return positionX;
	}

	public float getPositionY() {
		return positionY;
	}
	public void positionChanged(){
		setChanged();
		notifyObservers();
	}

	public void setPosition(float positionX, float positionY){
		this.positionX = positionX;
		this.positionY = positionY;
		positionChanged();
	}

	//기타메서드
}

package com.kws.javaobserver;
import java.util.Observable;
import java.util.Observer;

public class CurrentDisplay implements Observer, DisplayPosition{
	private float positionX;
	private float positionY;
	Observable observable;

	public CurrentDisplay(Observable observable) {
		this.observable = observable;
		observable.addObserver(this);
	}
	@Override
	public void display() {
		System.out.println("positionX : " + positionX + " positionY :" + positionY);
	}

	@Override
	public void update(Observable o, Object arg) {
		if (o instanceof Position) {
			Position position = (Position)o;
			this.positionX = position.getPositionX();
			this.positionY = position.getPositionY();
			display();
		}
	}
}

package com.kws.javaobserver;

public class positionStation {

	public static void main(String[] args) {
		Position positionData = new Position();
		CurrentDisplay currentDisplay = new CurrentDisplay(positionData);
		positionData.setPosition(121.5f, 25.6f);
		positionData.setPosition(122.5f, 123.6f);
		positionData.setPosition(56.5f, 77.6f);
	}

}
  • java.util.Observable의 단점
  • Observable은 클래스이기 때문에 서브 클래스를 만들어야 한다. 이미 다른 슈퍼 클래스를 확장하고 있는 클래스에 사용이 불가능하다.
  • Observable 인터페이스라는 것이 없기 때문에 자바에 내장된 Observer API하고 잘 맞는 클래스를 직접 구현하는 것이 불가능하다.

  • 해결법은 확장이 가능하면 Observable를 사용하고 확장할 수 없는 경우라면 직접 구현해서 사용해야 한다.

옵저버 패턴의 예

  • 리스너 : 예를 들어 한 버튼에 여러개의 리스너가 등록이 가능한데 옵저버 패턴의 정의대로 일대 다 관계로 정의 할 수 있다.