본문 바로가기
Architecture & Engineering/Design Pattern

[GoF Design Pattern] 2. 옵저버(Obsever) 패턴

by 신숭이 2023. 5. 29.

[GoF Design Pattern] 2. 옵저버(Obserer) 패턴

 

 

주제(Subject)를 구독하는 관찰자(Observer) 패턴. 여러 컴포넌트가 같은 주제를 구독하고 주제의 정보가 변경될 때, 각 컴포넌트는 즉각 업데이트를 받도록 한다. 주제(Subject)는 자신을 구독하고 있는 옵저버(Observer)들을 관리하고 옵저버는 주제에 의존적이다. 예를 들어 하나의 데이터 소스(날씨 서버)가 있고 여러 종류의 UI 화면이 날씨 정보를 갱신받아 업데이트한다고 할 때, 옵저버 패턴을 적용할 수 있겠다. 

옵저버 패턴은 구조가 매우 간단하고 이해하기도 편한 패턴이다. 쉽지만 여러 가지 중요한 디자인 원칙을 내포하고 있기 때문에 잘 공부해 두면 좋다. -> 개방-폐쇄 원칙(OCP) , 느슨한 결합(Loose Coupling)

 

 

옵저버 패턴(Observer Pattern)의 정의

 

한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체에게 연락이 가고 자동으로 내용이 갱신되는 방식으로 일대다(one-to-many) 의존성을 정의한다. 

 

 

Class Diagram

 

각 주제에는 옵저버를 등록(register) / 제거(remove) 할 수 있고, 주제(Subject)는 옵저버를 리스트로 관리한다. 
주제는 notifyObservers를 통해 Observer의 update를 호출하여 데이터를 갱신한다. 

 

 

Design Principle 4 : 상호작용하는 객체 사이에는 가능하면 느슨한 결합(Loose Coupling)을 사용해야 한다.

 

* 느슨한 결합(Loose Coupling) : 객체들이 상호작용할 수는 있지만 서로를 잘 모르는 관계

옵저버 패턴은 느슨한 결합의 훌륭한 예시이다. 느슨한 결합을 사용하면 시스템의 유연성이 아주 좋아진다. 옵저버 패턴이 만드는 느슨한 결합은 다음과 같다.

1. 주제(Subject)는 옵저버가 특정 인터페이스를 구현한다는 사실만 안다. 
특정 옵저버는 옵저버 인터페이스를 구현했을 뿐, 주제(Subject)는 자신을 구독중인 구상(Concrete) 클래스 옵저버가 구체적으로 뭘하는지 알 수도, 알 필요도 없다.

2. 옵저버는 언제든지 새로 추가/제거 할 수 있다.
주제는 옵저버 인터페이스를 구현한 객체의 목록에만 의존하기에, 언제든지 추가/제거해도 상관없다.

3. 새로운 형식의 옵저버를 추가할 때도 주제를 변경할 필요가 전혀 없다.
새로운 구상 클래스 옵저버를 추가(새로 인터페이스 구현한 클래스 코딩해서 짜기)하더라도 주제를 변경할 필요도, 미치는 영향도 없다. 

4. 주제와 옵저버는 서로 독립적으로 재사용할 수 있다.
주제나 옵저버를 다른 용도로 사용하더라도 서로 단단하게 결합되어있지 않기 때문에, 손쉽게 재사용이 가능하다.

5. 주제나 옵저버가 달라져도 서로에게 영향을 미치지 않는다.
예를 들어 옵저버에 몇 가지 UI 관련 기능을 추가하더라도, 주제에 미치는 영향은 없다.

 

 

 

Example (Simple Observer Pattern)

 


간단한 예시를 보자. 옵저버는 옵저버 인터페이스를 구현한다. 그리고 자신이 구독할 주제를 여러 가지 방식으로 가질 수 있지만, 아래 예시에서는 구성(Compostion, Has-A) 방식으로 가지고 있다. 보통 주제 레퍼런스(simpleSubject)를 다음과 같이 저장하면 옵저버 목록에서 탈퇴할때 사용할 수 있다.

public interface Observer {
	public void update(int value);
}

public class SimpleObserver implements Observer {
	private int value;
	private Subject simpleSubject;
	
	public SimpleObserver(Subject simpleSubject) {
		this.simpleSubject = simpleSubject;
		simpleSubject.registerObserver(this);
	}
	
	public void update(int value) {
		this.value = value;
		display();
	}
	
	public void display() {
		System.out.println("Value: " + value);
	}
}

 

주제는 주제 인터페이스를 구현한다. 주제 인터페이스는 다음과 같이 옵저버 리스트를 관리할 필수적인 함수들을 가지고 있어야 한다.
value가 바뀔때(setValue) 주제는 자신을 구독하고 있는 모든 옵저버에게 해당 데이터의 변화를 알린다.

public interface Subject {
	public void registerObserver(Observer o);
	public void removeObserver(Observer o);
	public void notifyObservers();
}

public class SimpleSubject implements Subject {
	private List<Observer> observers;
	private int value = 0;
	
	public SimpleSubject() {
		observers = new ArrayList<Observer>();
	}
	
	public void registerObserver(Observer o) {
		observers.add(o);
	}
	
	public void removeObserver(Observer o) {
		observers.remove(o);
	}
	
	public void notifyObservers() {
		for (Observer observer : observers) {
			observer.update(value);
		}
	}
	
	public void setValue(int value) {
		this.value = value;
		notifyObservers();
	}
}

 

이제 비즈니스로직을 보면 다음과 같다. 

public class Example {

	public static void main(String[] args) {
		SimpleSubject simpleSubject = new SimpleSubject();
	
		SimpleObserver simpleObserver = new SimpleObserver(simpleSubject);

		simpleSubject.setValue(80);
		
		simpleSubject.removeObserver(simpleObserver);
	}
}

 

Push 방식, Pull 방식 옵저버 

 

위 방식은 Push 방식의 옵저버이다. 주제가 일방적으로 옵저버에게 데이터를 보내고 있다. 위와 다른 방식으로  Pull 방식으로 구현하면 옵저버가 주제로부터 필요한 데이터만 당겨오도록 변경할 수 있다. (큰 변경은 없다)

Push 방식은 update(value)와 같이 매개변수를 가지고 있다. 이는 일방적으로 주제가 value를 전달하는 것을 뜻하는데, 
Pull 방식은 매개변수 없이 update()만 주제가 호출하고 각 옵저버에서 update에서 갱신받을 데이터를 설정한다. 다음과 같다.

public class SimpleObserver implements Observer {
    private Subject simpleSubject;
    ...

    public void update() {
        this.valueA = simpleSubject.getValueA(); 	// 필요한 데이터를 취사선택
        this.valueB = simpleSubject.getValueB();	// (물론 주제에서 이러한 인터페이스를 제공해야)
        display();
    }
}

 

댓글