옵저버 패턴

차동준·2022년 7월 15일
0

CS-디자인패턴

목록 보기
4/16
post-thumbnail

😁 옵저버 패턴을 들어가기 전에...


"스타크래프트"라는 게임에는 옵저버라는 유닛이 존재한다.
이 옵저버는 자신은 보이지 않으면서 남들을 관찰할 수 있는 특수한 능력을 가지고 있다.

게임을 해보셨다면 알겠지만, 이 유닛은 보통 적들의 진영을 관찰하고 싶을 때, 그 진영에 갖다놓고 계속 감시하는 용도로 사용한다.



👨‍💻 서론


오늘은 이 옵저버라는 인트로를 통해서 옵저버 패턴에 대해 알아보는 시간을 가지려고 한다.

"감시"를 하는 이유는 무엇일까
뭔가가 발생하거나 변화하는 것을 빠르게 캐치하기 위해 할 것이다. 혹은 잘하는지 못하는지 감시하는 경우도 있다.

마찬가지로, 옵저버 패턴에서도 무언가 발생하거나 변화하는 것을 캐치하여 이에 반응해 무언가 수행하기 위해서 옵저버를 둔다. 이것이 바로 옵저버 패턴이다.



👨‍💻 옵저버(Observer) 패턴이란?


주제(Subject)의 상태 변화가 있을 때 옵저버들에게 변화를 알려주는 디자인 패턴



🔎 옵저버 패턴의 구조


이러한 옵저버 패턴에는 주제(Subject)옵저버(Object)로 이루어져 있다.

주제는 우리가 중요하게 생각하여 관찰하고자 하는 객체이고
옵저버는 이를 관찰하면서 주제의 데이터가 바뀌는 등 변화가 생기면 알아차릴 수 있는 객체이다.

조금 더 구체적으로,
주제(Subject) 인터페이스와 옵저버(Observer) 인터페이스,
각각 이 둘을 구체적으로 구현한 클래스가 존재한다. ConcreteSubject와 ConcreteObserver이다.

주제(Subject)와 옵저버(Observer)를 예로 들면,
신문사와 그 신문을 구독하거나 구독하지 않는 사람인데
그 신문사를 구독하게되면(옵저버) 신문사에서는 매일 새로운 뉴스가 나올 때마다 구독한사람(옵저버)에게 그 소식이 전해지고 옵저버는 해당 뉴스를 확인할 수 있다.

하지만, 그 신문사를 구독하지 않는다면 해당 신문사에서 새로운 뉴스가 나와도 알 수 없다.



🔎 옵저버 패턴의 예시


interface Subject {
	public void register(Observer obj);
    public void unregister(Observer obj);
    public void notifyObservers();
    public Object getUpdate();
}
interface Observer {
	public void update();
}

이런식으로 Subject는 Observer를 등록, 삭제, 알림, 변화한 내용을 가져오는 등의 추상메서드가 있다.

추가로, 옵저버는 Subject의 변화 알림이 오면 update 메소드를 이용하여 변화에 대응한 행위를 할 수 있다.

예시를 좀더 들면,
아래와 같이 뉴욕타임즈 신문이라는 Subject가 있을 때,

class NewYorkTimes implements Subject {
	private List<Observer> observers;
    private String newsTitle;
    
    public NewYorkTimes() { // 생성자
    	this.observers = new ArrayList<>();
        this.newsTitle = "";
    }
    
    @Override // 옵저버 등록
    public void register(Observer obj) {
    	if (!observers.contains(obj)) observers.add(obj);
    }
    
    @Override // 삭제
    public void unregister(Observer obj) {
		observers.remove(obj);
    }
    
    @Override // 변화 알림
    public void notifyObservers() {
    	// *모든 옵저버에 대해 update 메소드 실행*
    	this.observers.forEach(Observer::update);
    }
    
    @Override // 변화된 뉴스 타이틀 내용을 가져오는 함수
    public Object getUpdate() {
    	return this.newsTitle;
    }
    
    public void postNews(String title) { // 새로운 뉴스의 타이틀 입력
    	System.out.println("새로운 뉴스 타이틀: ");
        this.newsTitle = title;
        notifyObservers();
    }
}

옵저버인 구독자 클래스가 아래와 같다.

class Subscriber implements Observer {
	private String name;
    private Subject newsPublisher; // Subject 타입의 신문사가 저장될 곳
    
    public Subscriber(String name, Subject newsPublisher) {
    	this.name; // 구독자 이름
        this.newsPubilsher = newsPublisher; // 신문사
    }
    
    @Override
    public void update() {
    	String msg = (String) newsPublisher.getUpdate(); // 구독한 신문사의 새로운 뉴스 제목을 가져오기
        System.out.println(name + ": " + msg); // 새로운 메시지 출력
    }
}

테스트 코드를 한번 보면,

public class Test {
	public static void main(String[] args) {
    	NewYorkTimes nyt = new NewYorkTimes();
        
        // 옵저버의 신문사 구독
        Observer A = new Subscriber(A, nyt);
        Observer B = new Subscriber(B, nyt);
        Observer C = new Subscriber(C, nyt);
        
        // 구독자 등록
        nyt.register(A);
        nyt.register(B);
        nyt.register(C);
        
        // 변화 생성
        nyt.postNews("새로운 뉴스 전달합니다!");
        // postNews 메소드 실행 후 출력 결과
        // A: 새로운 뉴스 전달합니다!
        // B: 새로운 뉴스 전달합니다!
        // C: 새로운 뉴스 전달합니다!
    }
}

이처럼 신문사의 새로운 뉴스가 등록되자마자 observer의 update함수가 실행되면서 새로운 뉴스의 타이틀을 가져와서 출력하게 된다.

이러한 방식이 바로 옵저버 패턴의 예시이다.



🔎 Loose Coupling(느슨한 결합) 이란?


위의 예시에서, 옵저버를 구현한 클래스의 인스턴스(구독자)를 얼마든지 만들어도 주제(Subject)인 신문사의 코드는 고칠 필요가 없고 옵저버 인스턴스(구독자)를 등록만 시켜주어도 서로 상호작용을 할 수 있게 된다.

이러한 경우를 우리가 느슨하게 결합되었다(Loose Coupling)고 표현하는데, 신문사를 아무리 추가하고 구독자를 아무리추가하고 서로 등록하고 삭제하여도 서로 독립적이면서 어떠한 코드도 변경할 필요가 없이 서로 상호작용이 가능하다.

반대로, 만약에 뉴욕타임즈 클래스 안에 A라는 구독자를 업데이트 시켜주는 코드가 직접적으로 들어가있다면?

// NewYorkTimes 클래스 내부...
public void updateNews() {
	updateSubscriberA() {
    	/// 구독자 A에게 업데이트 시켜주는 내용
    }
}

A라는 구독자가 구독을 취소했을 때, 우리는 뉴욕타임즈 클래스 안에 있는 A라는 구독자를 업데이트 시켜주는 코드를 찾아서 제거해주어야 한다.

이렇게 되면 유지보수도 힘들어질 뿐더러 코드가 더 복잡해질 수 있다.

그렇기 때문에 우리는 상호작용이 필요한 객체에 대해서 이 Loose Coupling의 개념을 잘 이해하고 사용해야 한다.



🔎 옵저버 패턴의 장단점


장점

  1. 실시간으로 변경사항을 인식하여 전달할 수 있다.
  2. Loose Coupling(느슨한 결합)으로 객체간의 의존성을 최소화할 수 있다.
  3. 옵저버를 독립적으로 언제든지 추가할 수 있고 그때마다 주제(Subject)를 변경할 필요가 없고, 서로 영향을 미치지 않는다.

단점

  1. Thread-Safe하지 않기 때문에, 옵저버를 등록 혹은 삭제하는 과정에서 원하는 결과가 나오지 않을 수 있다..
  2. 더이상 사용하지 않는? 혹은 필요없는 옵저버의 경우 제거해주어야 한다.(메모리 누수 발생)
  3. 너무 많은 옵저버가 생성되면 관리가 힘들다.
  4. 비동기 방식이기 때문에 원하는 순서대로 결과가 처리되지 않을 수 있다.
  5. 데이터 배분에 문제가 생길 시에 큰 문제가 발생할 수 있다.



♻️ 참고


https://kscory.com/dev/design-pattern/observer

profile
백엔드를 사랑하는 초보 개발자

0개의 댓글