본문 바로가기

Coding Note

[Design pattern] Observer

Observer Pattern : 객체들간 관계 중에 특정 객체의 상태 변화에 대해 실시간으로 반응해야 하는 객체가 있다. 이런 객체들의 상태변화를 관찰하기 위한 방법에 대해 쉽게 생각한다면 일정 시간마다 관찰 대상의 상태를 점검하여 반응하도록 할 수 있을것이다. 하지만 대상의 상태가 변하지 않았는데도 주기적으로 관찰해야한다면 프로그래밍적인 관점에서 그리 좋은 밥법이라고는 할 수 없다. 하드웨어 인터럽트가 있는것 처럼, 관찰 대상이 변했을때 "나 변했소!" 라고 알려준다면 불필요한 관찰행동을 없애며 상태의 변화에 따라 민감하게 반응할 수 있을것이다. 이러한 행위를 구조화 한 것이 Observer pattern이다.

 

관찰자(Observer)의 관점에서 구현을 고려한다면 처음 생각해 보았던 방법(일정 주기마다 관찰 대상 체크)뿐이 답일것이다. 하지만 관찰 대상(Observable)의 관점에서 고려한다면, 그리고 관찰 대상이 성실한 관찰대상이라고 가정할 수 있다면 관찰자는 불필요한 에너지 낭비를 크게 줄일 수 있을것이다. Observer pattern은 이와같이 관찰 대상의 관점에서 고안된 패턴이라고 할 수 있다.

 

 

관찰 대상들은 누가 자신을 관찰하는지 관찰자들의 목록을 가지고있으며, 관찰 대상이 늘어나면 그 관찰 대상에게 자신을 좀 봐달라고 알리게된다(Observable.attach). 반대로 관찰자가 더이상 관찰하지 않는다면 관찰 대상은 자신의 관찰자 목록에서 그를 빼버린다(Observable.detach). 그리고 중요한 부분으로 관찰 대상 자신의 상태가 변하여 자신을 봐주길 원한다면 관찰자들에게 그 사실을 알리며(Observable.notify), 관찰자들은 정신을 차리고 대상의 상태를 반영한다. 즉, 관찰자와 관찰대상이 N:1의 관계를 가지는 것이다.

게임 캐릭터중에 소환사 한명이 있다. 소환사의 크리쳐들은 똑똑하게도 소환사의 상태에따라 행동을 달리한다. 소환사가 관찰 대상이며 크리쳐들이 관찰자가된다. 이를 예제로 들어보자.

 

/* Observable.java */
public interface Observable{
public void attach(Observer o);
public void detach(Observer o);

public void notify();
}

 

/* Summorner.java */

public class Summorner implements Observable{

private int hp;

private ArrayList<Observer> creatures;


public Summorner(){

hp = 100;

creatures = new ArrayList<Observer>();

}


public void attach(Observer newCreature){

creatures.add(newCreature);

}


public void detach(Observer delCreature){

creatures.remove(delCreature);

}


public void notify(){

for(Observer o : creatures){

o.update( ((Object)this.hp) );

}

}


public void setHp(int newHp){

this.hp = newHp;

}

public int getHp(){

return this.hp;

}

public void damaged(int damHp){

this.hp -= damHp;

notify();

}

}


서머너는 자신의 상태를 관찰하는 크리쳐의 리스트를 가지고있고, 새로운 크리쳐와 함께하게 될 때마다 리스트에 추가해준다. 그리고 자신의 상태가 변경되었을때 크리쳐들에게 자신의 상태가 변경됬음을 알려준다.

 

/* Observer.java */
public interface Observer {
public void update(Object obj);
}
 
/* Golem.java */
public class Golem implements Observer {

private GolemAction golemAction;

public Golem(){

golemAction = new BasicGolemAction();

}

 

public void update(Object obj){
int summonerHp = ((Integer)obj);

if(summonerHp < 50){ 

golemAction = new BodyguardGolemAction();

}

 

public void doAction(){

golemAction.do();

}

골렘의 행동을 정의해보았다. 골렘은 서머너의 hp에 따라 행동을 달리하는데, hp가 50보다 적으면 조금 다른 행동을 하게하였다.

 

/* main */

public static void main(String[] args){

Summorner summorner = new Summorner();


summorner.attach(new FireGolem());

summorner.attach(new FireGolem());


while(summorner.getHp() > 0){

summorner.damaged(10);

}

}

 

이렇게 Observer pattern을 사용하면 상태의 변경에 따른 행동을 달리할 수 있고, 대상과 관찰자를 분리함으로써 결합도를 낮출 수 있다. 위의 예제에서 만약 다른 형태의 골렘을 소환한다 해도 서머너의 입장에서는 구조가 전혀 바뀌지 않는다. 마찬가지로 새로운 골렘을 만들때 관찰 대상에 대한 고려는 update될 때 뿐이다. Observer pattern이 얼마나 강력하지 보여주는 부분이다.

 

Observer pattern을 조금 더 상세하게 나눈다면, 위의 예제에서와 같이 관찰자에게 관찰 대상의 데이터를 넘겨주는 push communication method와 update(void) 인 메소드 안에서 에서 관찰 대상의 정보를 직접 활용하는 poll communication method 로 나눌 수 있다.


물론 골렘들은 서머너의 상태에만 반응하는 수동적인 생물이 되어버렸지만 서머너의 상태를 매번 확인하면서 행동하는것보단 서머너가 '으악!' 하고 말해줄때 행동하는게 더 효율적인 방법일것이다.


한줄요약 : 관심받고싶다면 관찰자들한테 나를 알려야한다.

'Coding Note' 카테고리의 다른 글

[Design pattern] Factory Method  (0) 2013.01.30
[Design pattern] Abstract Factory  (0) 2013.01.27
[Design pattern] Strategy  (0) 2013.01.19
[ExtJs4] nested model  (0) 2013.01.01
[MFC] MDI에서 child view 접근  (0) 2012.10.17