본문 바로가기

기타/객체지향의 탄생(2013)

객체지향의 탄생-Observer Pattern

 

  1. 옵저버 패턴(Observer)

1. 이제 좀더 다양한 객체 구조의 세계로 나아간다. 사람은 혼자 살수 없는 존재이다. 사람은 늘 주변의 소식에 관심을 갖고, 나라의 소식에 관심을 갖고, 세상 모든곳에서 벌어지는 소식에 관심을 갖는다. 그 소식에 따라 사람의 생각과 행동이 바뀌기도 한다. 사람은 언제나 무언가에 관심을 갖고 그 관심에 따라 자신의 생각과 행동을 결정한다.

 

그러므로 사람이 관심갖는 모든것에 어떻게 소식을 주고 받을지의 문제는 인류의 역사가 시작되면서 먹고 자는것만큼 중요한 화두가 되었다. 손짓발짓에서 언어가 탄생되고 문자가 탄생되어 소식을 주고 받기 시작했다. 동양에서는 봉화로 소식을 전하기도 했다. 말이나 낙타도 이용하여 소식을 전하기도 했다. 산업혁명이후 모르스 부호로 소식을 전하고 전화가 발명되고 무선통신으로 통신하고 지금에 와서는 TV, 라디오, 인터넷, 휴대폰등의 온갖 다양한 매체가 세계인에게 골고루 소식을 전파고 구독자는 그 소식을 보며 자신의 생각과 행동을 결정한다.

 

객체지향 프로그래밍은 사람의 세계를 옮겨놓았으므로 객체도 객체간의 소식을 주고받는 일이 필요하다. 객체가 혼자서 작동하는 경우는 드물다. 객체는 상속, 위임, 구성, 집합의 여러 관계로 묶여있으며 그 관계가 끊기면 객체의 존재 유무가 사라지기도 한다. 객체도 다양한 관계로 묶여있고 상호간에 소식을 전해야 한다.

 

객체간에 소식을 주고 받으려면 일단 주거나 받길 원하는 객체를 인자로 갖고 있어야 한다. 다음 소식을 전달할 경우 상대방 객체의 set메소드등을 호출한다. 소식을 받을때는 상대방 객체의 get메소드를 호출한다. 만약 소식을 주고 받길 원하는 객체가 많으면 의존해야 하는 객체는 급격하게 늘어난다. 이렇게 되면 객체지향에서 결벽증처럼 강조하는 낮은 결합도 높은 응집도를 어긋나게 하는 상황일 것이다.

 

만약 객체지향에 조금이라도 익숙한 설계자라면 이 상황을 어떻게 해결해야 하는지 알고 있다. 소식을 주거나 받길 원하는 객체 들을 첫번째, '가족'으로 만들어 두번째, 엄마 인터페이스에 의존하는 것이다. 이렇게 하면 인터페이스의 실제 구현 클래스를 몰라도 실행때 실제 구현 클래스를 인자로 받기 때문에 의존성을 줄일 수 있다.

 

그렇다면 이 인터페이스를 활용한 객체지향 기법을 이용하여 소식을 주는 객체와 소식을 받길 원하는 객체를 세련되게 정리하는 방법이 없을까. 옵저버 패턴이 정답이다.

 

2. 옛날 '프로그래머'란 검색어로 출력되는 뉴스를 읽어보기 위해 일단 포털 뉴스를 즐겨찾기 했다. 다음 생각날때마다 해당 뉴스페이지에 접속하여 '프로그래머'란 검색어를 수작업으로 입력하여 관련 뉴스를 찾아보았다. 원하는 정보야 결국 얻겠지만 너무 많이 수작업에 의존했다. 나중에는 결국 귀찮아서 프로그래머 관련 뉴스 보기를 포기했다. 본래 프로그래머 관련 기사를 검색하고자 했던 나름대로의 이유가 있을 것이지만 수작업 의존의 장벽이 포기하게 만든다.

 

3. 2007년 즈음에 웹2.0 열풍이 불었다. 웹2.0을 제대로 알면 진정한 인터넷 세상이 펼쳐지는 줄 알았다. 그때 RSS란 기술을 알게되었다. RSS는 XML기반의 사이트 정보를 배포하는데 RSS리더기를 통해 원하는 사이트의 RSS를 구독하면 RSS리더기가 주기적으로 해당 사이트의 RSS를 읽어와서 업데이트 한다. 나는 웹2.0의 여러기술중에서도 RSS를 으뜸으로 쳤다. 관심있는 사이트의 정보를 얻기위해서 과거 즐겨찾기 했을때는 수작업으로 사이트 방문하여 혹시 정보가 업데이트 되었는지 확인했지만, RSS구독을 하게 되면 RSS리더기가 알아서 최신 정보를 나에게 보여주기 때문이다.

 

이때 알고보니 포털의 뉴스도 검색어 결과에 나온 뉴스들을 RSS등록할수 있게 개발이 되어 있었다. 프로그래머 검색어 관련 뉴스를 RSS구독하여 이제는 내 일자리와 관련된 다양한 뉴스를 편하게 구독할수 있게 되었다.

 

4. 옛날 프로그래머 관련 뉴스를 읽기위해 수작업으로 해당 포털 사이트 들어가서, 수작업으로 검색어를 쳐야 정보를 획득할수 있었던 것은 다음과 같은 객체 구조와 비슷하다.

 

[사람 객체가 사이트에 직접 접속하여 정보획득]

 

직관적으로 이해되는 그림이다. 문제점도 직관적으로 보인다. 정보 제공 주체가 포털 사이트 객체인데 사람 객체가 포털 사이트 객체를 조작하게되어 포털 사이트에 정보가 갱신이 안된상태라면, 쓸데없이 정보를 가져오는 작업을 반복하는 문제가 발생된다.

 

만약 정보를 얻고자 하는 사이트가 10개 100개로 늘어난다면 문제는 10배 100배로 심각해진다. 10개, 100개의 사이트 객체에 의존해야 한다. 10개, 100개의 사이트객체의 정보를 수작업으로 검색해야 한다. 최악의 경우 10개중에 9개는 정보가 갱신된 상태가 아닐수도 있다. 다른 경우로 사람 객체가 여러명이라면 두 객체간의 의존도는 거미줄처럼 복잡해질것이다.

 

[사람이 알고자 하는 정보가 많을때마다 의존하는 사이트도 늘어난다.]

 

해결방법의 출발은 사람 객체와 정보 제공 사이트 객체를 인터페이스화 시키는 것이다.

 

[조금 더 객체지향적으로 개선]

 

이제 의존성 문제는 해결이 되었다. 하지만 정보 관찰자가 정보를 수작업으로 획득하는것이 아니라 정보 제공자가 주도적으로 정보를 제공해야 한다.

 

[옵저버 패턴 그림]

    public static void main(String[] args) {

        

        // 정보 제공자(Observer) 생성

        SiteSubject subject = new SiteConcreateSubject();

        

        // 정보 관찰자(Subject) 생성, 생성할때 정보 관찰자가 의존하는 정보 제공자를 같이 넘겨준다.

        HumanObserver observer = new HumanConcreateObserver(subject);

        

        // 정보 제공자(Observer)에 관찰자 등록

        subject.registerObservers(observer);

        

        // 정보 제공자(Observer)에 의존하는 관찰자에게 update를 실행하는 메소드 실행

        subject.notifyObservers();

    }

[메인 클래스, 정보 제공자 클래스와, 관찰자를 생성하고 정보 제공자에 관찰자를 등록한다. 정보 제공자의 관찰자의 상태를 update하는 메소드가 있다.]

 

public abstract class HumanObserver {

 

    SiteSubject subject;

    

    public HumanObserver(SiteSubject inSubject) {

        this.subject = inSubject;

    }

    

    public abstract void update();

}

[옵저버 수퍼 클래스, 정보 제공자의 수퍼 클래스를 알고 있다.]

 

public class HumanConcreateObserver extends HumanObserver {

    

    public HumanConcreateObserver(SiteSubject inSubject) {

        super(inSubject);

    }

 

    @Override

    public void update() {

        String value = subject.getState();

        System.out.println("사이트의 정보를 얻어옵니다. ["+value+"]");

    }

}

[옵저버를 상속받은 구상 클래스, update메소드를 호출받으면 자기가 알고 있는 정보제공자의 상태 정보를 얻어온다.]

 

public abstract class SiteSubject {

    

    List observerList = new ArrayList();

String stateInfo = "여기 정보가 있습니다.";

 

    /**

     * 관찰자(Observer)를 등록한다.

     * @param observer

     */

    public void registerObservers(HumanObserver observer) {

        observerList.add(observer);

    }

    

    /**

     * 관찰자(Observer)를 삭제한다.

     * @param observer

     */

    public void removeObservers(HumanObserver observer) {

        observerList.remove(observer);

    }

    

    /**

     * 관찰자(Observer)의 update 메소드를 실행한다.

     */

    public void notifyObservers() {

        for(int i = 0; i < observerList.size(); i++) {

            HumanObserver observer = (HumanObserver)observerList.get(i);

            observer.update();

        }

    }

    

    /**

     * 자신 정보제공자(Subject)의 상태를 알려준다.

     * @return

     */

    public abstract String getState();

    

    /**

     * 자신 정보제공자(Subject)의 상태를 셋팅한다.

     * @param value

     */

    public abstract void setState(String value);

}

[정보 제공자 클래스, 관찰자의 등록, 삭제, 정보 제공(notify), 상태 정보를 제공하고 셋팅하는 메소드들로 구성된다.]

 

public class SiteConcreateSubject extends SiteSubject {

 

    @Override

    public String getState() {

 

        return stateInfo;

    }

 

    @Override

    public void setState(String value) {

        stateInfo = value;

    }

}

[정보 제공자 상속 클래스, ]

 

위의 구성으로 객체 구조를 개선했다. 정보 관찰자를 정보 제공자 객체안 리스트에 더하기만 하면 정보 제공자의 정보가 갱신되었을때 자동으로 업데이트 된 정보를 모든 정보 관찰자에게 전달하게 된다. 그래서 정보 제공자가 주도적으로 정보를 관찰자에게 제공할 수 있다.

 

5. 문제덩어리 정보교환 객체 구조에서 깔끔한 옵저버 패턴으로 진화되기까지의 과정을 살펴보자. 먼저 객체지향의 전형적인 기술인 인터페이스/추상클래스로 분리하는 과정을 통해 의존도를 낮추었다. 다음 정보제공자가 정보를 제공하고 싶을때 주도적으로 정보관찰자에 정보를 전달하게 하기 위해 옵저버 패턴을 활용하였다.

 

정보제공자의 정보를 구독하길 원하는 관찰자들은 정보제공자의 관찰자 속성에 컬렉션으로 저장된다. 작동중에 다른 관찰자를 컬렉션에 더하기 할수 있으며 정보제공을 원하지 않는 관찰자 객체가 있다면 그 객체만 컬렉션에서 삭제할수 있다.

 

일단 관찰자 그룹의 인터페이스에만 의존되어 있기때문에 구체적인 관찰자는 모른상태에서 컬렉션에 얼마든지 저장할수 있고, 관찰자 컬렉션을 자유자재로 다룰수 있으며, 정보 제공자 자신의 정보가 업데이트 되었을때 관찰자 컬렉션에 등록된 모든 컬렉션에 자신의 업데이트된 정보를 전달할 수 있다.

 

여기서 쓰인 중요한 객체지향 디자인 원리는 '서로 상호작용 하는 객체 사이에는 가능하면 느슨하게 결합되는 디자인을 사용한다' 이다. 두 객체가 느슨하게 결합되어 있다는 것은 그 둘이 상호작용을 하긴 하지만 서로에 대해 최대한 모른다는 것을 의미한다. 이 디자인 원리를 구현하는 핵심은 비슷한 기능을 하는 객체를 패밀리로 묶고 인터페이스/추상 클래스를 만들어 클라이언트 객체가 인터페이스/추상 클래스에 의존하게 하는 것이다.

 

위의 즐겨찾기 프로그램은 결국 느슨하게 결합되는 디자인을 사용했기 때문에, 객체 사이의 상호의존성을 최소화했기 때문에, 어떠한 변경 사항이 생겨도 무난하게 자신의 할일을 수행할수 있는 유연한 객체지향 어플리케이션을 구축할수 있다.

 

옵저버패턴은 이 원리에다가 추상화된 객체에 의존한 컬렉션의 자유로운 조작으로 유연하고 깔끔하게 정보를 전달하는 기법을 담고 있다.

 

[옵저버 패턴 원본]

 

 

덧 ) 이 객체지향의 탄생 원고는 제가 책으로 내려다가 일단 잘 안되었는데요. 이유는 비문이 많다. 단락내 주제가 중복된다. 어떤 상황 설명을 과장한다.등 입니다. 그래도 원고를 일단 블로그에 몽땅 풀어보고 언젠가 제대로 교정해서 다시 도전할 생각입니다. 이점을 감안해서 읽고 객체지향을 이해하는데 도움이 되셨으면 좋겠습니다. 의견도 주셨으면 좋겠습니다. 원고 조금만 교정하면 괜찮을것 같은 출판사 관계자분의 피드백도 환영합니다. 특별한 일 없으면 매주 월수 발행 예정입니다.