본문 바로가기

개발세발/Design pattern

Observer 패턴


옵서버 패턴(observer pattern)은 객체의 상태 변화를 관찰하는 관찰자들, 즉 옵저버들의 목록을 객체에 등록하여 상태 변화가 있을 때마다 메서드 등을 통해 객체가 직접 목록의 각 옵저버에게 통지하도록 하는 디자인 패턴이다. 주로 분산 이벤트 핸들링 시스템을 구현하는 데 사용된다. 발행/구독 모델로 알려져 있기도 하다. -위키피디아

역시 개요만 봐선 뭔소린지 못알아듣겠다. 위키백과를 잘 보면 다이어그램과 구현 예시가 나와있긴 한데, 알고 보면 정말 설명을 잘 해놓았지만 모르고 보면 이게 뭐지 싶다. 대부분의 블로그들 강좌를 보면 위키백과 내용과 크게 다르지가 않다. 그래서 역시 이게 뭐지 싶다. (그나마 어떤 분은 팬과 아이돌의 관계로 설명을 해주셨다)

Add인지 구독인지 뭔지를 하고..발행을 하고..관찰자를 객체에 붙여...상태 변화...끝나면 언레지스터...콜백....이것이 바로 감시자 패턴입니다!!

Observer패턴에 대해 처음 알아봤을 때 위와 같은 내용을 읽었고 도데체 이게 뭐하는거지라는 생각밖에 안들었다. 발행/구독은 또 뭐고 옵저버를 등록한다는데 그럼 옵저버는 뭐고 객체의 상태 변화는 또 뭐하는거지.

옵서버 패턴 할 때 Observer는 여러분이 알고있는 스타의 그 옵저버가 맞다. 한국어로는 '감시자' 또는 '관찰자'정도로 번역이 되며, 정식적인 한국어 표기는 '옵서버'라고 하는게 맞다고 한다. 진짜 그대로 설명하면, 어떤 객체의 상태 변화를 감지하는 감시자를 옵저버 리스트에 등록하고, 상태 변화가 일어나면 리스트의 옵저버들에게 통보를 하는 패턴이 Observer패턴이다.


.....


Observer패턴이 쓰일 수 있는 좋은 예를 하나 들어봐야겠다.
보통 파일을 인터넷에서 다운로드하면 다운로드바가 뜨고, 해당 파일을 얼마나 다운받았는지 시각적으로 표현한다. (50% 받았으면 막대의 절반이 차오르는 등), 그리고 그 옆에 정확히 몇퍼센트 받았는지 숫자로도 표기를 해준다. 어떤 웹브라우져는 다운로드 관리용 창이 또 있어서, 모든 다운로드중인 파일의 상태를 또 알려주기도 한다. 이럴 때, 얼마나 받았는지 표기하는 개체들(Progress bar, Text)들은 어떻게 실시간으로 다운로드 상태 정보를 갱신해서 보여줄까? 

다운로드 창(window)이 오직 1개라면, 다운로드 모듈과 개체들을 1:1로 연결해버리면 된다. 하지만 '하나의 다운로드'를 '여러 창'이 볼 수 있게 된다면 1:1로 연결할 수 없다.
여러 창이라는게 무슨말이냐면, 웹브라우져로 큰 파일 다운로드 하나 하고, 창을 여러개 띄운 후에 각 창에서 다운로드관리자를 켜보면 된다. 분명 다운로드는 1개만 되고 있는데, 띄워져있는 모든 창에서 다운로드 상태가 갱신이 된다.

이런 상황을 구현하기 위한 것이 바로 Observer패턴이다. '다운로드 모듈'안에 옵저버 리스트를 만들고, 1초마다 한번씩 옵저버 리스트 안의 옵저버들에게 다운로드 상태를 알려주는 것이다.
옵저버는? 각각의 창이 옵저버가 된다. 한 창이 켜질 때마다 그 창을 다운로드 모듈의 리스트에 추가한다. 창이 꺼지면 리스트에서 삭제한다. 이렇게 되면, 창이 켜질 때마다 다운로드 모듈의 리스트에 등록이 되고, 다운로드 모듈은 파일을 받을때마다 모든 창에게 다운로드 상태를 알려주게 된다.


컴퓨터에서 '알려준다'는 개념이 좀 이해가 안될 수 있다. 이 글에서 '알려준다'는, 그냥 '파라미터(매개변수)에 값을 넣어 함수를 호출한다'정도가 된다. 즉, 다운로드 모듈은 다운로드할 때마다 매개변수에 다운로드 상태를 담아서 리스트에 있는 객체들의 함수를 호출한다. 위 그림과 아래 그림은 똑같은 내용을 담고있다.

현실에서도 아주 굉장히 유사한 사례가 있다.

과제 공지용 단톡방을 생각해보자.

대학교에서 교수님이 '이제부터 모든 과제는 단체톡방을 이용하겠습니다. 톡방에 초대하겠습니다'라고 하셨다. 모든 학생은 단톡방에 초대가 되고, 교수님이 단톡방에 과제를 올리면 학생들은 톡방을 확인한 후 과제를 한다.
여기서, 톡방에 있는 학생 목록이 옵저버 리스트이며, 톡방에 글을 올리는 것은 '과제를 해라'라는 함수를 호출하는 것이다. 학생들의 핸드폰은 옵저버가 되며, 학생들은 옵저버를 통해 과제 상태 변화를 확인한 후 과제를 하는 것이다.


설명은 끝났다. 이제 이것을 Java를 이용해서 구현해보자. (C#은 나중에. 지금 프로젝트에서 Java를 하고있기도 하고, 요즘 대학교에선 취업시킨다고 Java기본문법만 4학년까지 주구장창 가르친다고 한다. C#은 Events와 Delegate를 사용해서 구현할 수 있다. 위키백과에 예제가 있음)

위에서 설명한 다운로드 모듈을 만들 것이다.
util.Observable을 쓰지 않고 Interface를 이용해서 구현해볼 것이다. 클래스와 인터페이스 포함 총 5개의 파일을 작성한다.
(다운로드모듈 class, 창 class, Main class, Observer interface, publisher interface)


1. 다운로드모듈 class (교수-학생 설명에서는 '교수님'에 해당하는 class)

import java.util.ArrayList;

public class DownloadModule implements IDownloadPublisher
{
	//옵저버 목록. 교수-학생 설명에서는 단톡방의 참가자 리스트에 해당된다.
    ArrayList<IDownloadObserver> observers;
    
    String filename;
    int percent;

    //생성자
    public DownloadModule(String filename)
    {
    	observers = new ArrayList<>();
        this.filename = filename;
        percent = 0;
    }

    //옵저버를 리스트에 추가한다. 교수-학생 설명에서는 단톡방에 학생이 들어오는 것에 해당된다.
    @Override
    public void add(IDownloadObserver observer)
    {
    	observers.add(observer);
    }

    //옵저버를 리스트에서 제거한다. 교수-학생 설명에서는 단톡방에서 학생이 나가는 것에 해당된다.
    @Override
    public void delete(IDownloadObserver observer)
    {
    	observers.remove(observer);
    }

    //옵저버들에게 상태를 알린다. 교수-학생 설명에서는 단톡방에 교수님이 '톡 보내기'버튼을 누르는 것에 해당된다.
    @Override
    public void update()
    {
    	for(IDownloadObserver observer : observers)
    	{
    		observer.update(filename, percent);
    	}
    }
    
    //상태를 업데이트한다. 교수-학생 설명에서는 교수님이 단톡방에 '과제하세요'라고 타이핑하는 것에 해당된다.
    public void updateState()
    {
    	System.out.println("Download state updated");
    	percent += percent<100?5:0;    //예시를 위해 상태가 갱신될 때마다 5%씩 증가하는걸로 한다.
    	update();
    }
}

2. 창 class (교수-학생 설명에서는 '학생'에 해당하는 class)

class Window implements IDownloadObserver
{
    String name;
    public Window(String windowName)
    {
        name = windowName;
    }

    //창의 상태를 업데이트한다. 교수-학생 설명에서는 학생들이 교수님의 톡을 본 후 행동하는 것에 해당된다.
    @Override
	public void update(String filename, int percent)
    {
        System.out.print(name + " - ");
        System.out.println(filename + " Downloading... " + percent + "%");
    }
}

3. Main class (실행용)

public class DownloadObserve { public static void main(String[] args) { //다운로드 모듈. (교수님) DownloadModule downModule = new DownloadModule("file1.zip"); //다운로드 상태를 받아볼 창 생성 (학생들) Window w1 = new Window("w1"); Window w2 = new Window("w2"); Window w3 = new Window("w3"); Window w4 = new Window("w4"); //각 창들을 옵저버 리스트에 등록 (학생들이 단톡방에 참가한다) downModule.add(w1); downModule.add(w2); downModule.add(w3); downModule.add(w4); //for문이 돌 때마다 다운로드모듈의 상태를 갱신시켜준다. (과제를 한다) for(int i=0; i<20; i++) downModule.updateState(); //다운로드가 완료되었으니 옵저버 해제 (과제를 끝냈으니 학생들이 단톡방에서 나간다) downModule.delete(w1); downModule.delete(w2); downModule.delete(w3); downModule.delete(w4); } }

4. Observer interface (교수-학생 설명에서는 단톡방->학생들의 폰에 해당하는 interface)

public interface IDownloadObserver
{
    public void update(String filename, int percent);
}

5. Publisher interface (교수-학생 설명에서는 교수님의 폰->단톡방에 해당하는 interface)

public interface IDownloadPublisher
{
    public void add(IDownloadObserver observer);
    public void delete(IDownloadObserver observer);
    public void update();
}


실행해보면, 리스트에 등록된 Window들이 모두 함수를 호출한다. 이렇게 어떤 한 개체의 상태 변화를 다른 개체들에게 바로바로 알리고 싶을 때 사용하는 것이 Observer 패턴이다.
Interface를 따로 구현한 이유는, 꼭 Window class가 아닌 다른 클래스를 사용하더라도 그 클래스에 해당 interface를 implements(구현)만 해준다면 된다. 즉, 한 개체의 상태 변화를 '다양한' 다른 개체에게 전달할 수 있어진다.

'개발세발 > Design pattern' 카테고리의 다른 글

Observer 패턴  (2) 2017.10.09
디자인 패턴  (0) 2017.10.09