Observer 패턴에 다루는 마지막 포스팅입니다. 


정말, 이 패턴에 대해 할 말이 많군요...

그만큼 많이 사용하고 유명한 패턴이기 때문에 그런 듯 합니다.

(그렇다고, Observer 패턴은 사랑은 아닙니다...     ㅡㅡ^)


Observer 패턴과 관련된 정보는 아래에서 참고!



앞써, 언급한 바와 같이 Observer 패턴은 매우 많이 사용하는 패턴입니다.

그렇기 때문에 JAVA 를 만든 개발자들은 이 패턴을 보다 적극적으로 활용할 수 있도록, JDK1.0 부터 모듈을 배포하였습니다.


오늘 포스팅에서는 지난 포스팅에서 만든 예제를 자바의 Observer 패턴 관련 클래스를 이용하여 리팩토링해보려 합니다. 대상 프로젝트는 "[CHAPTER 5] 실무 활용 패턴 (하) [Observer 패턴] + 추가내용2" 하단에서 다운로드 할 수 있습니다.


일단, 자바에서 제공하는 내장 모듈인 Observable 과 Observer 부터 소개를 해보겠습니다.



1. 자바 내장 모듈 [Observable 과 Observer]


앞써, 알아봤던 Observer 패턴에서는 크게 두 개의 컴포넌트가 있음을 확인했습니다.

바로 주제(Subject)와 관찰자(Observer) 입니다. 기억이 나시나요?


자바 내장 모듈은 이 [두 개의 컴포넌트]들을 쉽게 제작할 수 있도록 지원하고 있습니다.


- Observable [java.util.Observable]


주제(Subject)에 해당하는 컴포넌트를 만들 수 있도록 지원하는 클래스입니다. 

변화를 통보할 대상인 [Observer 인터페이스]목록을 Thread-safe 하게 관리하며, 통보할 수 있는 메소드를 지원하고 있습니다.


통보하는 메소드는 물론 PUSH, PULL 방식 모두 지원하도록 설계되어 있습니다.


1
2
3
4
5
// PULL 방식 notify. (아무것도 보내지 않는 방식)
observable.notifyObservers();
        
// PUSH 방식 notify. (아쉽게도, 통보할 때 보내는 데이터 타입은 한 개로 제한..)
observable.notifyObservers(memberList);
cs


편리해보이지만, 주의해볼만한 사항은 Observable 이 class 라는 점입니다.

Observer 인터페이스들을 관리해야하는 로직을 담아야하기 때문에 interface 로 제작할 수는 없었던 듯 합니다.


- Observer [java.util.Observer]


관찰자(Observer)에 해당하는 컴포넌트를 만들 수 있도록 지원하는 인터페이스입니다.

이 전 예제의 IObserver 를 대체할 수 있겠군요.


Observer 의 내부를 보면, 아래와 같습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
public interface Observer {
    /**
     * This method is called whenever the observed object is changed. An
     * application calls an <tt>Observable</tt> object's
     * <code>notifyObservers</code> method to have all the object's
     * observers notified of the change.
     *
     * @param   o     the observable object.
     * @param   arg   an argument passed to the <code>notifyObservers</code>
     *                 method.
     */
    void update(Observable o, Object arg);
}
cs


파라미터로는 [주제에 해당하는 Observable o] 과 [Push 방식을 통해 전달되는 데이터인 Object args] 이 존재 합니다. 


[Object args]의 주석을 보면, Observable::notifyObservers 를 통해 넘겨지는 데이터가 온다고 써있습니다. 즉, PUSH 방식으로 통보를 하면 통보 시 전달한 데이터가 넘어오고, PULL 방식을 사용하면 NULL 임을 짐작할 수 있을 것 같습니다.



쉽게 사용할 수 있도록 제작된 모듈이니, 이 정도면 한 번 모듈을 사용해서 Observer 패턴을 구현해 볼 수 있을 것 같군요. 


한번, 지난 포스팅에서 작성한 예제를 바탕으로 리팩토링을 한번 해볼까요? 



2. 내장 모듈을 사용한 리팩토링


이미 우리는 Observer 패턴을 구현하였기 때문에 JDK 의 내장모듈을 쉽게 부착할 수 있을 지도 모릅니다. 


먼저 우리가 작성했던 주제(Subject) 컴포넌트인 DataManager 부터 손을 보도록 하죠.


DataManager 클래스는 Observable 클래스를 상속받을 예정이며, 이를 통해 Observer 를 관리하던 메소드 및 멤버변수도 삭제할 것입니다. 


저는 아래와 같이 만들어 봤습니다. (정확히 말하면, 기능 삭제만 했습니다. @.@)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
/**
 * 메신저에서 사용하는 데이터를 총괄적으로 관리하는 데이터 매니저.
 *
 * <pre>
 *     Observable 를 상속!
 *     그러나, 추 후 DataManager 가 상속받아야 하는 클래스가 있으면 문제 발생.
 *     (JAVA 는 다중상속을 할 수 없음..)
 * </pre>
 *
 * Created by Doohyun on 2017. 6. 16..
 */
public class DataManager extends Observable{
 
    // 통보대상 집합 (삭제. Observable 클래스가 지원)
    // private HashSet<IObserver> iObserverSet = new HashSet<>();
 
    /**
     * 옵저버 추가
     *
     * <pre>
     *     Deprecated 사유 : Observable::addObserver 를 통해 이 기능을 대체 가능.
     *
     *     삭제할 것.
     * </pre>
     *
     * @param observer
     */
    @Deprecated
    public void registerObserver(IObserver observer) {
      //  iObserverSet.add(observer);
    }
 
    /**
     * 옵저버 삭제
     *
     * <pre>
     *     Deprecated 사유 : Observable::deleteObserver 를 통해 이 기능을 대체 가능.
     *
     *     삭제할 것.
     * </pre>
     *
     * @param observer
     */
    @Deprecated
    public void deleteObserver(IObserver observer) {
     //   iObserverSet.remove(observer);
    }
 
    /**
     * 각 옵저버에게 현재 상태 데이터를 넘기면서, 변경사실을 알린다.
     *
     * <pre>
     *     Deprecated 사유 : Observable::notifyObservers 를 통해 이 기능을 대체 가능.
     *
     *     삭제할 것.
     * </pre>
     *
     */
    @Deprecated
    public void notifyObserver() {
     /*   for (IObserver observer : iObserverSet) {
           observer.update(this);
        }*/
    }
 
   /**
     * 서버로부터 데이터 로드
     *
     * <pre>
     *     각 정보 목록들은 한 트랜잭션에서 동시에 갱신된다고 가정.
     * </pre>
     *
     * @param memberVoList
     * @param approvalDocList
     * @param messageVoList
     */
    public void loadByServer(List<MemberVo> memberVoList, List<ApprovalDoc> approvalDocList, List<MessageVo> messageVoList) {
        // something work
 
        // 변화가 일어났음을 알림. (이 메소드 호출을 통해 자주 통보하는 것을 막을 수 있음)
        setChanged();
 
        // Observable::notifyObservers 기능 사용. PULL 방식
        notifyObservers();
    }
}
cs


Observer 를 관리하는 서명들은 위에서 모두 주석 혹은 사장(Deprecated) 처리가 되었지만, 최종적으로 삭제할 것입니다. 


Observable 을 상속받음으로써, DataManager 는 서버로부터 전달된 데이터만 업데이트 하고,  Observable::setChanged와 Observable::notifyObservers 만 사용해주면 되는군요. 


여기서, 주목할 것은 Observable::setChanged로 상태 변화가 일어났음을 나타내는 메소드 입니다. 

이 메소드를 통해 너무 자주 통보되는 것을 막을 수 있으며, 이를 호출하지 않고 Observable::notifyObservers 만 호출하면 아무 일도 일어나지 않습니다. 

적절한 비지니스 로직으로 Observable::setChanged 를 제어할 수 있을 것 입니다.


즉 구현에 있어서 Observer 관련 내용을 신경쓰지 않아도 됩니다.



이제 관찰자(Observer) 컴포넌트를 수정할 차례입니다. 


우리는 이전에 IObserver 를 구현하는 데모 View 컴포넌트를 만든 적이 있습니다. 

이제는 Observer 를 구현하도록 수정하고, IObserver 와 작별을 할 것입니다. :-)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class DemoViewComponent_V1 implements Observer {
 
    /**
     * 화면 갱신
     *
     * @param o
     * @param arg
     */
    @Override
    public void update(Observable o, Object arg) {
        if (instanceof DataManager) {
            final DataManager dataManager = (DataManager) o;
 
            // 구성원 정보 업데이트.
            {
                // 유연한 구성원 데이터 조회
                final List<MemberVo> memberVoList = dataManager.getMemberList();
                viewUpdateByMember(memberVoList);
            }
 
            // 전자결재 정보 업데이트.
            {
                // 유연한 전자결재 데이터 조회
                final List<ApprovalDoc> approvalDocList = dataManager.getApprovalDocList();
                viewUpdateByApprovalDoc(approvalDocList);
            }
        }
    }
}
cs


일단, DataManager 의 서명이 변경되었으니 테스트 코드 쪽에서도 불이 났을 것입니다. 

테스트 코드 역시 조금 수정을 할 필요가 있겠군요. 


새 메소드 서명에 따라 다음과 같이 변경할 수 있어 보입니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 데이터 매니저 생성
DataManager dataManager = new DataManager();
 
// 데모 View 컴포넌트 생성.
DemoViewComponent_V1 demoViewComponent_v1 = new DemoViewComponent_V1();
DemoViewComponent_V2 demoViewComponent_v2 = new DemoViewComponent_V2();
 
// 각 컴포넌트들이 Data 변경알림에 대해 구독하겠다고 등록.
dataManager.addObserver(demoViewComponent_v1);
dataManager.addObserver(demoViewComponent_v2);
 
// something work
dataManager.loadByServer(
        Arrays.asList(new MemberVo("강현지"), new MemberVo("유덕형"), new MemberVo("유형주"))
        , Arrays.asList(new ApprovalDoc("통과 - 스터디 신청"))
        , Arrays.asList(new MessageVo("모두, 스터디 참여 잘해줘서 감사합니다."), new MessageVo("그동안 수고하셨습니다.")));
 
 
// CONSOLE LOG
// 데모2 메시지 View 화면 갱신 중..
// [모두, 스터디 참여 잘해줘서 감사합니다.] 갱신 중
// [그동안 수고하셨습니다.] 갱신 중
// 데모2 메시지 View 화면 갱신 완료!!
// 
// 데모1 구성원 View 화면 갱신 중..
// [강현지] 갱신 중
// [유덕형] 갱신 중
// [유형주] 갱신 중
// 데모1 구성원 View 화면 갱신 완료!!
// 
// 데모1 전자결재 View 화면 갱신 중..
// [통과 - 스터디 신청] 갱신 중
// 데모1 전자결재 View 화면 갱신 완료!!
cs


다행히 DataManager 의 변화에 따라 View 컴포넌트들이 적절한 행위를 잘 해주는 군요. :-)



3. 자바 내장 모듈의 한계와 극복


Observable 과 Observer 를 사용하면 편리하게 Observer 패턴을 사용할 수 있음을 확인하였습니다. 

하지만, 이 내장 모듈에는 한계가 존재합니다. Observable 이 클래스라는 것이죠. 


Observable 을 개발한 개발자의 의도는 Observable 을 상속받길 원했겠지만, 아쉽게도 DataManager 에 상속을 해야할 일이 있다고 가정해봅시다.


Java 에서는 다중상속을 지원하지 않으니, Observable 을 상속하는 것을 포기해야 합니다.

또한 상태의 변화가 있다고 체크하는 Observable::setChanged 는 protected 입니다. 상속이 힘들다고, DataManager 에 Observable 인스턴스를 만들어 관리한다 하더라도 Observer 들에게 통보를 할 수 없습니다. 


이는, 설계원칙 Favor has-a over is-a (상속해야하는 것을 has-a 로 바꿀 수 있어야 한다.)에 위배됩니다. 

안타깝군요. ㅜㅡㅜ


하지만 조금 돌아가면 방법이 없지는 않습니다.

Observable 을 상속한 다른 클래스(이를테면, MessengerObservable) 을 만들고, DataManager 가 MessengerObservable 을 has-a 관계로 취할 수 있을 것 같습니다.


저는 이렇게 작성을 해보았습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/**
 * 메신저에서 사용할 Observable 클래스
 *
 * <pre>
 *     DataManager 에 직접 상속을 할 수 없기 때문에, 해당 클래스를 제작.
 *     DataManager 가 Observable 을 사용하기 위해서는 이 모듈을 사용해야함.
 * </pre>
 *
 * Created by Doohyun on 2017. 6. 18..
 */
public class MessengerObservable extends Observable{
 
    /**
     * Observer 에게 통보
     *
     * <pre>
     *     - setChanged 메소드는 해당 클래스에서 제어.
     *     - Observer 들이 DataManager 에 접근할 수 있도록 PUSH 방식 Observer 패턴을 사용.
     * </pre>
     */
    @Override
    public void notifyObservers(Object dataManager) {
        this.setChanged();
 
        // PUSH 방식 Observer 패턴 사용을 통해 DataManager 를 넘김.
        super.notifyObservers(dataManager);
    }
 
}
 
/**
 * 메신저에서 사용하는 데이터를 총괄적으로 관리하는 데이터 매니저.
 *
 * <pre>
 *     Observable 를 상속불가 우회
 *     - MessengerObservable 를 has-a 관계로 처리.
 * </pre>
 *
 * Created by Doohyun on 2017. 6. 16..
 */
public class DataManager extends SomethingSuperObject{
 
    private MessengerObservable messengerObservable = new MessengerObservable();
 
    /**
     * 추가 메소드 지원.
     *
     * <pre>
     *     - MessengerObservable 에 Observer 추가.
     * </pre>
     *
     * @param observer
     */
    public void addObserver(Observer observer) {
        messengerObservable.addObserver(observer);
    }
 
    /**
     * 삭제 메소드 지원.
     *
     * <pre>
     *     - MessengerObservable 에서 Observer 삭제.
     * </pre>
     *
     * @param observer
     */
    public void deleteObserver(Observer observer) {
        messengerObservable.deleteObserver(observer);
    }
 
    /**
     * 서버로부터 데이터 로드
     *
     * <pre>
     *     각 정보 목록들은 한 트랜잭션에서 동시에 갱신된다고 가정.
     * </pre>
     *
     * @param memberVoList
     * @param approvalDocList
     * @param messageVoList
     */
    public void loadByServer(List<MemberVo> memberVoList, List<ApprovalDoc> approvalDocList, List<MessageVo> messageVoList) {
        // something work!!
 
        // PUSH 방식으로 우회.
        messengerObservable.notifyObservers(this);
    }
}
 
/**
 * 데모 View 갱신 컴포넌트 (V1)
 *
 * <pre>
 *     - 구성원과 전자결재 화면을 업데이트.
 * </pre>
 *
 * Created by Doohyun on 2017. 6. 17..
 */
public class DemoViewComponent_V1 implements Observer {
 
    /**
     * 화면 갱신
     *
     * @param o
     * @param arg
     */
    @Override
    public void update(Observable o, Object arg) {
 
        if (arg instanceof DataManager) {
            final DataManager dataManager = (DataManager) arg;
 
            // 구성원 정보 업데이트.
            {
                // 유연한 구성원 데이터 조회
                final List<MemberVo> memberVoList = dataManager.getMemberList();
                viewUpdateByMember(memberVoList);
            }
 
            // 전자결재 정보 업데이트.
            {
                // 유연한 전자결재 데이터 조회
                final List<ApprovalDoc> approvalDocList = dataManager.getApprovalDocList();
                viewUpdateByApprovalDoc(approvalDocList);
            }
        }
    }
}
 
cs


주목할 점은 MessengerObservable 이 PUSH 방식으로 DataManager 를 전달하기 때문에, Observer 구현체들은 두번째 파라미터인 Object arg 를 통해 DataManager 를 찾아야 합니다.


드디어, Observer 패턴과 관련된 모든 내용이 끝이 났고, 동시에 이번 스터디에서 진행예정이던 모든 패턴이 끝이 났습니다. (ㅜㅡㅜ)


이 포스팅의 결과물 프로젝트는 아래에서 참고! 



STUDY_OOP7.zip



마지막 패턴이야기라 그런지 유종의 미를 잘 거두고 싶었습니다. 

그래서 더욱 자세히 쓰고자 이 패턴에 대해 세 개의 포스팅으로 나누어 작성 했었던 것 같습니다.


주 독자와 나누는 마지막 패턴 이야기라 조금 섭섭하기도 하기도 하고, 더 알려줄 것이 없나 고민도 하게 되었던 것 같습니다. 그래서 빨리 끝낼 수 있는 걸 질질 끌었던 것 같기도.....


특히 최근에 마지막 스터디를 진행하기 전 날, [대학교 1학년]-[SW멤버십 회원]-[M사 신입사원]-[스터디 진행]까지의 있었던 일들이 주마등처럼 지나갔었습니다. 


[문과출신 공대생]이라는 핸디캡 극복을 위해 노력했었고, [4학년 동기들 중 2학년이었던 SW멤버십]에서 끝까지 살아남기 위해 발버둥을 쳤으며, [M사 신입사원] 시절 사내에서 아무도 하지 않았기 때문에 누군가의 도움도 기대할 수 없는 솔루션급 앱들을 밤새 만들고 고치며 끝내 좌절하던 경험들은 저에게 뼈아픈 교훈들만 주었던 것 같습니다.


최근에 읽었던 칼럼 중 [지금의 실력은 지식의 총합이 아닌 고통의 총합] 이라는 글은 이 때의 일을 주마등처럼 지나가게 한 촉매가 아니었나 생각이 듭니다. 


하지만, 이런 뼈아픈 교훈들은 안타깝게도 급격한 실력의 변화를 주지는 못하던 것 같습니다. 


최근에 읽었던 [소프트웨어 장인], [구글 리쿠르팅 데이의 타 회사 세션] 등의 내용을 참고하면, 앞에서 끌어주는 [메이저 주니어 이상급의 개발자]들의 활동은 무시할 수 없어보였습니다. 이를테면 페어 프로그래밍이나 코드 리뷰, 본질에 가까운 OOP&디자인 패턴 교육 등은 주니어들의 레벨을 올리는 것에 크게 기여하는 것 같아 보였습니다.


결론은 모두가 뼈아픈 교훈을 얻어갈 필요는 없는 것 같습니다. :-)

이 블로그의 글들은 대단하지 않으며 꽃길로 인도할 수도 없지만, 조금이나마 진흙을 덜 밟는데 도움이 되길 바랍니다.


감사합니다.










반응형
Posted by N'

지난 포스팅에서는 특정 주제의 변화에 따라 여러 클래스가 어떤 행위를 해야할 때, 즉 이벤트 처리를 하기 위한 대표적인 패턴인 Observer 패턴에 대해 알아보았습니다.


간단한 예제 프로그램도 한 번 작성해보았죠? :-)


해당 내용과 관련된 정보는 아래에서 참고! 



이번 포스팅에서는 지난 스터디에서 작성한 코드에 대한 약간의 리팩토링을 담을 예정입니다.


조금 더 유연한 구조를 한번 제시해 볼 생각이며, 작업 대상이 되는 프로젝트는 "실무 활용 패턴 (하) [Observer 패턴] + 추가내용" 의 하단에서 다운로드 하실 수 있습니다.


일단, 고민이 되는 이슈부터 기술을 해보도록 하죵...



1. IObserver 인터페이스의 메소드 서명은 적절한가?


구체화 된 View 컴포넌트들에게 DataManager 의 상태 변화를 알리고자, DIP 원리에 따라 추상적인 개념인 IObserver 를 만들었습니다.


아래와 같이 말이죠..


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
 * 옵저버 인터페이스 제작
 *
 * Created by Doohyun on 2017. 6. 17..
 */
public interface IObserver {
 
    /**
     * 주체의 데이터가 변경될 때, 이 메소드를 실행
     *
     * <pre>
     *     - 업데이트 이벤트를 이 메소드를 통해 받을 수 있음.
     *     - 변경된 데이터가 파라미터로 넘어오는 것을 보장. 이 방식 PUSH 방식 옵저버 패턴이라고 함.
     * </pre>
     *
     * @param memberVoList
     * @param approvalDocList
     * @param messageVoList
     */
    void update(List<MemberVo> memberVoList, List<ApprovalDoc> approvalDocList, List<MessageVo> messageVoList);
}
cs


DataManager 는 통보할 대상을 IObserver 로 관리함으로써 각 ViewComponent 들과의 직접적인 의존관계를 끊을 수 있었고, ViewComponent 들 역시 IObserver 를 구현함으로써 DataManager 에게 쉽게 구독여부를 등록할 수 있었습니다.


하지만, 이런 고민을 해볼 수 있습니다.


- 구체화된 View 컴포넌트들은 사실 모든 정보가 필요하지 않습니다. 

  

테스트로 구현해본 [DemoViewComponent_V1 은 구성원정보와 전자결재 정보]만 필요했습니다. 또한 [DemoViewComponent_V2 는 메시지 정보]만 필요했죠..


- 데이터 형식 추가에 취약해보입니다.


현재 배포할 버전에서는 구성원, 전자결재, 메시지 정보만 필요하지만, 추 후 근태정보, 평가정보 등 다른 정보가 추가되어야 한다면 IObserver::update 서명을 수정해야합니다.


인터페이스 서명의 변경은 구현된 모든 클래스들도 변경해야 함을 의미합니다.


결국, DataManager 의 통보를 하기 위한 방식에 조금 문제가 있어보이네요.

큰 구조에서 봤을 때 Subject-Observer 의 관계는 잘 나누었지만, 디테일한 부분을 조금 더 리팩토링해주면 좋을 것 같습니다.


다행히, 아직 View 컴포넌트들이 많이 생성되지 않았으니 구조를 조금 변경해볼까요? :-)



2. 통보방식의 변경


현재 IObserver:update 의 메소드 서명을 보면, 상태의 변화사실과 함께 변경된 데이터를 같이 전달하는 방식입니다. 지난 시간에 이 방식을 PUSH 방식의 Observer 패턴이라고 설명을 했었습니다.


하지만 이 방식은 결국 전달할 데이터들을 메소드 서명에 명시해야하며, 이는 전달할 데이터 형식에 유연할 수 없음을 의미합니다. 유연할 수 없는 데이터 형식 때문에 데이터 형식의 추가/삭제가 힘들고, 모든 관찰자(Observer)는 원하지도 않는 데이터들을 강제로 받아야 하죠.


그렇다면, 관찰자들이 조금은 능동적으로 본인이 원하는 정보만 취할 수 있다면 어떨까요?

주제(Subject)는 관찰자들에게 변경사실만 알리고, 관찰자들이 알아서 주제로부터 상태를 조회하는 방식이죠.


인터페이스를 다음과 같이 변경해보겠습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
 * 옵저버 인터페이스 제작
 *
 * Created by Doohyun on 2017. 6. 17..
 */
public interface IObserver {
 
    /**
     * 주체의 데이터가 변경될 때, 이 메소드를 실행
     *
     * <pre>
     *     - 업데이트 이벤트를 이 메소드를 통해 받을 수 있음.
     *     - 이 메소드에서는 직접적으로 변경된 데이터를 보내지 않음.
     *     - Subject 인스턴스를 전달하여, 관찰자들이 능동적으로 원하는 방식을 취하는 방식.
     *     - 이 방식 Pull 방식 옵저버 패턴이라고 함.
     * </pre>
     *
     * @param dataManager
     */
    void update(DataManager dataManager);
}
cs


변경된 IObserver::update 는 통보와 함께 변경된 데이터들을 전달하는 것이 아닌, 주제 클래스를 전달하고 있습니다. 


각 IObserver 들의 구현체들은 변경을 통보 받을 시, 전달받은 주제(DataManager) 클래스로부터 원하는 데이터를 유연하게 조회할 수 있을 것입니다. 

이와 같이, 주제는 변경에 대한 통보만 하고 데이터는 관찰자들이 주제로부터 직접적으로 데이터를 조회하는 방식 PULL 방식 옵저버 패턴이라고 합니다.


구성원 데이터와 전자결재 데이터가 필요했던, DemoViewComponent_V1 을 저는 다음과 같이 수정해 보았습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/**
 * 데모 View 갱신 컴포넌트 (V1)
 *
 * <pre>
 *     - 구성원과 전자결재 화면을 업데이트.
 * </pre>
 *
 * Created by Doohyun on 2017. 6. 17..
 */
public class DemoViewComponent_V1 implements IObserver {
 
    /**
     * 화면 갱신.
     *
     * @param dataManager
     */
    @Override
    public void update(DataManager dataManager) {
        // 구성원 정보 업데이트.
        {
            // 유연한 구성원 데이터 조회
            final List<MemberVo> memberVoList = dataManager.getMemberList();
            viewUpdateByMember(memberVoList);
        }
 
        // 전자결재 정보 업데이트.
        {
            // 유연한 전자결재 데이터 조회
            final List<ApprovalDoc> approvalDocList = dataManager.getApprovalDocList();
            viewUpdateByApprovalDoc(approvalDocList);
        }
    }
 
    /**
     * 구성원 화면 업데이트
     *
     * @param memberVoList
     */
    public void viewUpdateByMember(List<MemberVo> memberVoList) {
        System.out.println("데모1 구성원 View 화면 갱신 중..");
 
        for (MemberVo memberVo : memberVoList) {
            System.out.printf("[%s] 갱신 중\n", memberVo.getName());
        }
 
        System.out.println("데모1 구성원 View 화면 갱신 완료!!\n");
    }
 
    /**
     * 전자결재 화면 업데이트
     *
     * @param approvalDocList
     */
    public void viewUpdateByApprovalDoc(List<ApprovalDoc> approvalDocList) {
        System.out.println("데모1 전자결재 View 화면 갱신 중..");
 
        for (ApprovalDoc memberVo : approvalDocList) {
            System.out.printf("[%s] 갱신 중\n", memberVo.getTitle());
        }
 
        System.out.println("데모1 전자결재 View 화면 갱신 완료!!\n");
    }
}
 
cs


PULL 방식 옵저버 패턴을 통해, 각 View 컴포넌트들은 원하는 데이터들만 주제로부터 유연하게 조회할 수 있겠군요. 


또한 데이터 형식이 추가된다 하더라도 IObserver 인터페이스를 수정할 필요가 없습니다. 

더이상 IObserver:update 에서 데이터 형식에 대한 서명을 명시할 필요가 없기 때문이죠.


이와 같이 상태변화를 통보하는 방식에 따라 PUSH 와 PULL 방식이 존재합니다. 

실전에서 이 패턴을 쓰기 전에, 이 두 방식에 대해 조금 더 Zoom-In 해서 볼 필요가 있을 것 같네요.



3. PUSH vs PULL


앞써, 기술한 것과 같이 상태변화 통보 방식에 따라 Observer 패턴을 [PUSH 과 PULL], 두 방식으로 크게 나눌 수 있습니다. 


두 방식에는 다음과 같은 특징이 존재합니다.


- PUSH


주제가 상태변화를 통보를 할 때, 관찰자에게 변화된 정보까지 제공하는 방식입니다.


관찰자들은 주제가 어떤 녀석인지 알 필요도 알고 싶지도 않습니다. 

단지, 무언가에 의해 통보사실과 함께 관심있는 데이터를 이용해 원하는 행위를 취하면 되죠.

소프트웨어 공학적으로 말하면, 모듈 간의 결합도(Coupling)를 낮출 수 있습니다.

좋은 프로그램은 모듈 간 낮은 결합도와 높은 응집성을 갖는 것이 이상적 입니다.


하지만, 통보받은 새 정보 중에는 알고 싶지 않은 정보도 포함되어 있습니다. 

또한 정보타입의 추가에 유연할 수가 없습니다.


- PULL


주제는 단지 상태 변화만을 통보하며, 변화된 정보는 관찰자가 주제로 부터 별도로 정보를 요구하는 방식입니다.


관찰자 입장에서는 원하는 정보를 유연하고 편리하게 요구할 수 있습니다.

또한, 정보 타입 추가에도 별도로 관찰자들을 수정을 하지 않아도 됩니다. (OCP 하군요..)


하지만, 관찰자들은 주제로 부터 정보를 별도로 요구해야 하기 때문에 주제에 대해 많은 것을 알아야 합니다. 즉 모듈 간 결합도가 증가했습니다. (tight-Coupling)


극단적인 경우, 동시성 처리를 할 시 제공받는 정보의 질이 다를 수 있습니다. 

[상태 변화 당시의 정보]와 [별도로 요구할 때의 정보]가 Thread-safe 하지 않을 수 있습니다. 


보통 권장하는 스타일은 변화에 보다 유연한 PULL 방식의 옵저버 패턴이지만, 실전에서 필요한 요구사항에 따라 적절한 방식을 선택해야 겠죠? 



오늘 포스팅에서는 추가로 제안되는 PULL 방식의 Observer 패턴을 다뤄 보았습니다.


변경된 소스는 아래 프로젝트 파일을 참고!


STUDY_OOP7.zip


Observer 패턴 자체도 많이 사용하기 때문에 중요한 패턴이지만, PUSH vs PULL 의 개념 등은 패턴 외에도 큰 모듈들 간의 관계 자체에서도 흔하게 등장하기 때문에 더욱 소개하고 싶었습니다.


또한, 지난 포스팅에서 다룬 이벤트 감지 방법인 Polling vs Interrupt 개념도 중요합니당..


흔히, 사용하는 Source-Tree 도 [프로젝트 상태 변경 사실을 표시]하고, 우리가 [PULL 을 받도록] 하죠? 프로젝트 상태 변경 사실은 [주기적으로 체크하는 Polling 방식]을 사용합니다.


이렇게 우리가 흔히 사용하는 응용 프로그램에서도 이 개념들을 많이 찾아볼 수 있습니다.


그래서 이번 주제는 단지, 디자인 패턴 뿐 아니라 소프트웨어 공학적인 여러 개념까지 다뤄 볼 수 있는 기회 였던 것 같아 뿌듯하네요.. ㅎㅎ


이 글을 보는 많은 분들에게 도움이 되었으면 좋겠습니다.:-)


반응형
Posted by N'

이번 스터디에서는 마지막으로 다룰 패턴은 상태의 변화를 관찰하는 관찰자(Observer)를 등록하여 특정 주제(Subject)의 변화에 따라 관찰자들이 어떤 행위를 할 수 있도록 관리할 수 있는 Observer 패턴에 대해 알아 봤습니다.


수많은 응용 프로그램에서는 어떤 변화, 즉 이벤트 기반의 처리를 상당히 많이 다루며, 그렇기 때문에 Observer 패턴의 중요성과 사용성에 대해 관심을 가져볼 필요가 있다고 생각하여 마지막 주제로 정하게 되었습니다.


해당 포스팅과 관련된 정보는 아래에서 참고 



Observer 패턴에 대해 알아보기 전에 소프트웨어 공학에서 말하는 이벤트와 이를 다루는 방식에 대해 알아볼 필요가 있습니다. 깊게 토끼굴로 들어가지 말고, 간단히 알아보록 하죠. :-)



1. 이벤트와 처리방식.


이벤트(Event)란 어떤 상태의 변화를 말할 수 있을 것 같습니다.


예를들어 웹 사이트의 버튼을 누르는 행위나 스크롤을 내리는 등의 사용자가 UI(User Interface)를 이용하는 것 혹은 그 외의 날짜의 변경, 어떤 임계 수치의 도달상태의 변화를 이벤트라고 할 수 있으며, 상태 변화에 따라 어떤 행위를 수행하도록 하는 것을 이벤트 처리라고 할 수 있을 것 같습니다.


이런 상태변화를 감지하여, 어떤 행위를 하는 것은 매우 중요합니다. 

하지만 그 전에 이벤트를 감지하는 것이 먼저 선행되어야 할 것 같군요. 


소프트웨어 공학에서는 크게 두 가지 방식으로 상태 변화를 감지하는 법을 말합니다.



- 폴링(Polling) 방식


어떤 요구하는 상태가 되었는지를 주기적으로 감지하는 방식입니다. 


즉 CPU 는 일정 시간마다(이를 테면, 하루정도) 주기적으로 데이터를 가진 주체의 상태를 체크하여 행위를 수행합니다. 


예를들면, [SMS 를 보내는 메시지 스케줄러]는 30초마다 보내야할 메시지가 존재하는지를 관리 Model 로부터 확인하며, 메시지가 존재하면 일괄적으로 보냅니다.


하지만, 이 방식은 CPU 가 이벤트 감지에 대해서 주기적으로 체크해야하는 비용이 존재하며 어떤 이벤트가 발생했을 때 즉시 대응할 수 없겠죠?



- 인터럽트(interrupt) 방식


폴링 방식은 관찰자가 능동적으로 특정 주기를 가지고 상태를 체크하는 반면, 인터럽트 방식은 관찰자가 수동적으로 통보만 받는 방식입니다. 


주체(Subject)는 어떤 상태가 되었을 때 관찰자들에게 상태 변화를 통보하며, 관찰자들은 받은 통보에 따라 어떤 행위를 수행합니다.


이 방식은 CPU 가 이벤트를 감시해야하는 비용이 없으며, 어떤 변화에 대해서 즉시 대응도 가능합니다. 



Observer 패턴은 각 컴포넌트(class 혹은 특정 모듈 정도라고 생각...)들의 관계들을 이러한 인터럽트 방식으로 관리할 수 있도록 제시하는 패턴입니다. 


이번 리뷰에서는 주어진 특정 요구사항에 대하여 이벤트를 처리하는 과정을 통해 Observer 패턴을 정리해볼까 합니다.



2. 메신저의 데이터 변화에 따른 UI 변경 문제.


[욕심 많은 기획자]가 이번에는 사내메신저에 대한 기획서를 들고왔습니다.


이번에 베포할 사내 메신저 버전에는 구성원정보, 메시지 히스토리, 전자결재 목록 등이 있으며, 초보 개발자인 당신은 서버에서 전달받은 이 데이터들이 변화할 때마다 화면을 갱신 하라는 이슈를 받게 되었습니다.


갱신할 화면은 정해지지 않았고, 아직 개발도 되지 않았습니다. (하하하하.....)


하지만, 그동안 [변화에 유연했던 당신]은 좋은 구조를 만들 수 있을 것이라 생각합니다.


요구사항을 받았으니 한번, 따라가며 구현을 해봅시다. :-)



3. 데이터를 관리하는 주체(Subject) 클래스의 제작.


첫 번째로 고려할 사항은 데이터의 관리 문제입니다. 


데이터를 각 화면(View)에 해당하는 컴포넌트 마다 가지고 있다면, 서버로 부터의 데이터 갱신은 쉽지 않을 것입니다. 데이터 자체는 한 곳에서 관리하여 각 화면이 이 곳을 참고한다면, 서버에서 데이터 업데이트 문제는 Simple 해질 것 같군요.


데이터를 관리하는 클래스를 저는 이렇게 만들어 봤습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
 * 메신저에서 사용하는 데이터를 총괄적으로 관리하는 데이터 매니저.
 *
 * Created by Doohyun on 2017. 6. 16..
 */
public class DataManager {
 
    // 구성원 정보 목록
    private ArrayList<MemberVo> memberList = new ArrayList<>();
 
    // 전자결재 문서 목록
    private ArrayList<ApprovalDoc> approvalDocList = new ArrayList<>();
 
    // 메시지 목록
    private ArrayList<MessageVo> messageVoList = new ArrayList<>();
 
    /**
     * 서버로부터 데이터 로드
     *
     * <pre>
     *     각 정보 목록들은 한 트랜잭션에서 동시에 갱신된다고 가정.
     * </pre>
     *
     * @param memberVoList
     * @param approvalDocList
     * @param messageVoList
     */
    public void loadByServer(List<MemberVo> memberVoList, List<ApprovalDoc> approvalDocList, List<MessageVo> messageVoList) {
 
        // 구성원 데이터 업데이트
        {
            this.memberList.clear();
            this.memberList.addAll(memberVoList);
        }
 
        // 전자결재 데이터 업데이트
        {
            this.approvalDocList.clear();
            this.approvalDocList.addAll(approvalDocList);
        }
 
        // 메시지 업데이트
        {
            this.messageVoList.clear();
            this.messageVoList.addAll(messageVoList);
        }
    }
}
cs


DataManager 는 [구성원목록, 전자결재목록, 메시지 목록] 등 실제 데이터를 가지고 있으며, 서버로부터 데이터를 갱신하는 DataManager::loadByServer 메소드를 지원합니다.


모든 View 컴포넌트들은 이 곳에서 데이터를 조회하여 화면을 그릴 수 있겠군요. :-)



4. 데이터 변화에 대한 화면 갱신 처리.


이번 포스팅의 주제인 데이터 변화에 대한 화면 갱신을 할 때가 왔습니다. 


일단 데이터의 변화시점을 주목 해봅시다.


DataManager::loadByServer 가 실행이 되었다면, 서버로부터 새로운 데이터를 받았다고 할 수 있습니다. 즉, 이 메소드가 실행될 때 각 화면 컴포넌트들에게 어떤 액션을 취한다면 변화를 통보할 수 있고, 화면갱신을 할 수 있을 것 입니다.


하지만, 현재 단계에서는 아쉽게도 View 컴포넌트들이 모두 제작되지 않았습니다.

각 View 컴포넌트에 무슨 데이터가 화면 갱신에 필요한지도 모르며, 심지어 컴포넌트 개수 조차 알지 못합니다. 즉, DataManager 는 이벤트 발생을 알릴 대상을 관리하기에 애매한 상황입니다.


하지만, 우리는 이런 모호한 상황에서 어떻게 해야할지 알고 있습니다.


한번 지금의 문제를 생각해 볼까요?


1) DataManager 는 각 View 컴포넌트에 의존하고 있습니다. 


이벤트에 대해 알림을 해야하기 때문에, View 컴포넌트를 사용해야 합니다.


DataManager 는 데이터를 가진 주체(상위모듈)이고 이를 기반으로 View 컴포넌트들의 화면을 갱신(하위모듈)을 하는 구조라고 했을 때, 상위모듈이 하위모듈에 의존하고 있는 구조입니다.


2) 각 View 컴포넌트들은 구체적입니다.


View 컴포넌트들은 이미 구현이 완료된 구체화 클래입니다. 


우리는 지금의 상황에서 프로그램 설계원칙 중 DIP(의존성 역전 원칙)을 고려해볼 수 있습니다.


["상위모듈이 하위모듈에 의존하면 안된다. 상위모듈, 하위모듈 모두 추상에 의존해야 한다."]


기억 나시나요? 이 원칙에 따라 다음과 같은 UML 을 고려해 보겠습니다. :-)

 

 


UML 에서는 Observer(관찰자)라는 추상개념을 만들었습니다. 


그리고, 상위모듈인 DataManager 와 각 View Component 들이 모두 이 인터페이스에 의존하는 것을 표현하고 있습니다.


이 UML 에 따라 저는 아래와 같이 Observer 인터페이스를 제작하고, DataManager 에 통보대상에 대한 관리 기능을 추가했습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
/**
 * 옵저버 인터페이스 제작
 *
 * Created by Doohyun on 2017. 6. 17..
 */
public interface IObserver {
 
    /**
     * 주체의 데이터가 변경될 때, 이 메소드를 실행
     *
     * <pre>
     *     - 업데이트 이벤트를 이 메소드를 통해 받을 수 있음.
     *     - 변경된 데이터가 파라미터로 넘어오는 것을 보장. 이 방식을 PUSH 방식 옵저버 패턴이라고 함.
     * </pre>
     *
     * @param memberVoList
     * @param approvalDocList
     * @param messageVoList
     */
    void update(List<MemberVo> memberVoList, List<ApprovalDoc> approvalDocList, List<MessageVo> messageVoList);
}
 
public class DataManager{
 
     // 통보대상 집합
    private HashSet<IObserver> iObserverSet = new HashSet<>();
 
    /**
     * 옵저버 추가
     *
     * @param observer
     */
    public void registerObserver(IObserver observer) {
        iObserverSet.add(observer);
    }
 
    /**
     * 옵저버 삭제.
     *
     * @param observer
     */
    public void deleteObserver(IObserver observer) {
        iObserverSet.remove(observer);
    }
 
    /**
     * 각 옵저버에게 현재 상태 데이터를 넘기면서, 변경사실을 알린다.
     */
    public void notifyObserver() {
        for (IObserver observer : iObserverSet) {
            observer.update(memberList, approvalDocList, messageVoList);
        }
    }
 
    /**
     * 서버로부터 데이터 로드
     *
     * <pre>
     *     각 정보 목록들은 한 트랜잭션에서 동시에 갱신된다고 가정.
     * </pre>
     *
     * @param memberVoList
     * @param approvalDocList
     * @param messageVoList
     */
    public void loadByServer(List<MemberVo> memberVoList, List<ApprovalDoc> approvalDocList, List<MessageVo> messageVoList) {
        //// something work
        notifyObserver();
    }
}
cs


DataManager 의 입장에서는 추상개념인 IObserver 에 의존을 함으로써, 통보대상 관리를 위해 View 컴포넌트들을 알 필요성이 없어졌습니다. 


단순히 관찰자 컬렉션을 관리하면서 데이터 변경시점인 DataManager::loadByServer 가 실행될 때, DataManager::notifyObserver 도 같이 실행하여 관찰자들에게 변경사실을 통보해주면 됩니다. 이 때, 통보와 같이 업데이트할 데이터를 같이 넘겨주는 방식을 PUSH 방식이라고 합니다.


View 컴포넌트들의 입장에서는 IObserver 만 구현하고 DataManager 에게 변경사실에 대한 알람을 구독하겠다고 해주면(DataManager::registerObserver), 데이터 변화에 대한 실시간 화면갱신을 할 수 있습니다.


데모 테스트를 위해, 아래와 같이 정보를 이용해서 화면을 갱신을 하는 구체화된 View 컴포넌트 데모 클래스를 만들었습니다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
/**
 * 데모 View 갱신 컴포넌트 (V1)
 *
 * <pre>
 *     - 구성원과 전자결재 화면을 업데이트.
 * </pre>
 *
 * Created by Doohyun on 2017. 6. 17..
 */
public class DemoViewComponent_V1 implements IObserver{
 
    /**
     * 화면 갱신
     *
     * @param memberVoList
     * @param approvalDocList
     * @param messageVoList
     */
    @Override
    public void update(List<MemberVo> memberVoList, List<ApprovalDoc> approvalDocList, List<MessageVo> messageVoList) {
        viewUpdateByMember(memberVoList);
        viewUpdateByApprovalDoc(approvalDocList);
    }
 
    /**
     * 구성원 화면 업데이트
     *
     * @param memberVoList
     */
    public void viewUpdateByMember(List<MemberVo> memberVoList) {
        System.out.println("데모1 구성원 View 화면 갱신 중..");
 
        for (MemberVo memberVo : memberVoList) {
            System.out.printf("[%s] 갱신 중\n", memberVo.getName());
        }
 
        System.out.println("데모1 구성원 View 화면 갱신 완료!!\n");
    }
 
    /**
     * 전자결재 화면 업데이트
     *
     * @param approvalDocList
     */
    public void viewUpdateByApprovalDoc(List<ApprovalDoc> approvalDocList) {
        System.out.println("데모1 전자결재 View 화면 갱신 중..");
 
        for (ApprovalDoc memberVo : approvalDocList) {
            System.out.printf("[%s] 갱신 중\n", memberVo.getTitle());
        }
 
        System.out.println("데모1 전자결재 View 화면 갱신 완료!!\n");
    }
}
 
/**
 * 데모 View 갱신 컴포넌트 (V2)
 *
 * <pre>
 *     - 구성원과 전자결재 화면을 업데이트.
 * </pre>
 *
 * Created by Doohyun on 2017. 6. 17..
 */
public class DemoViewComponent_V2 implements IObserver{
 
    /**
     * 메시지 업데이트
     *
     * @param memberVoList
     * @param approvalDocList
     * @param messageVoList
     */
    @Override
    public void update(List<MemberVo> memberVoList, List<ApprovalDoc> approvalDocList, List<MessageVo> messageVoList) {
        viewUpdateByMessage(messageVoList);
    }
 
    /**
     * 메세지 화면 업데이트
     *
     * @param messageVoList
     */
    public void viewUpdateByMessage(List<MessageVo> messageVoList) {
        System.out.println("데모2 메시지 View 화면 갱신 중..");
 
        for (MessageVo memberVo : messageVoList) {
            System.out.printf("[%s] 갱신 중\n", memberVo.getMessage());
        }
 
        System.out.println("데모2 메시지 View 화면 갱신 완료!!\n");
    }
}
cs


필요한 기능은 모두 제작 되었으니, 제가 만든 코드가 잘 동작하나 테스트 코드를 작성해보고 퇴근하면 될 것 같군요. 


테스트 코드를 만드는 것은 매우 중요합니다. :-)


아래와 같은 테스트 코드를 간단하게 만들고자 합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 데이터 매니저 생성
DataManager dataManager = new DataManager();
 
// 데모 View 컴포넌트 생성.
DemoViewComponent_V1 demoViewComponent_v1 = new DemoViewComponent_V1();
DemoViewComponent_V2 demoViewComponent_v2 = new DemoViewComponent_V2();
 
// 각 컴포넌트들이 Data 변경알림에 대해 구독하겠다고 등록.
{
    dataManager.registerObserver(demoViewComponent_v1);
    dataManager.registerObserver(demoViewComponent_v2);
}
 
// something work
 
// Server update virtual Test!!!!
dataManager.loadByServer(
          Arrays.asList(new MemberVo("강현지"), new MemberVo("유덕형"), new MemberVo("유형주"))
          , Arrays.asList(new ApprovalDoc("통과 - 스터디 신청"))
          , Arrays.asList(new MessageVo("모두, 스터디 참여 잘해줘서 감사합니다."), new MessageVo("그동안 수고하셨습니다.")));
 
// CONSOLE LOG
// 데모1 구성원 View 화면 갱신 중..
// [강현지] 갱신 중
// [유덕형] 갱신 중
// [유형주] 갱신 중
// 데모1 구성원 View 화면 갱신 완료!!
// 
// 데모1 전자결재 View 화면 갱신 중..
// [통과 - 스터디 신청] 갱신 중
// 데모1 전자결재 View 화면 갱신 완료!!
//
// 데모2 메시지 View 화면 갱신 중..
// [모두, 스터디 참여 잘해줘서 감사합니다.] 갱신 중
// [그동안 수고하셨습니다.] 갱신 중
// 데모2 메시지 View 화면 갱신 완료!!
cs


다행히, 잘 작동을 하는군요. 


DataManager 의 데이터 변경에 따라(DataManager::loadByServer), 모든 View 컴포넌트 (DemoViewComponent_V1, DemoViewComponent_V2) 들은 적절한 행위를 수행하고 있습니다. 


즉, DataManager 의 데이터만 잘 관리해주면 모든 View 컴포넌트의 화면을 자동으로 잘 갱신할 수 있음을 의미합니다. 


또한, 갱신할 화면 추가를 위해 다른 코드를 수정하지 않아도 됩니다. 

만약, View 컴포넌트가 새로 추가되었고 데이터 변경에 따라 화면 갱신을 해야한다면, IObserver 를 구현하게 하고 DataManager 에 등록만 해주면 됩니다. (OCP 도 지켜진 듯 합니다.)


Observer 패턴의 핵심은 [한 객체의 상태 변화가 얼마나 많은 객체에 영향을 주어야 할지 알 수 없을 때] DIP 원리에 따라 구현을 함으로써 유연하게 대처하는 것입니다. 

(사실 관찰자들은 데이터에 따라 무슨 행위를 할지 모르기 때문에 언제나 구체적이며 다양할 수 밖에 없고, 그렇기 때문에 interface 를 이용해 추상적으로 묶어 관리를 하죠.) 


이 패턴은 또한 대표적인 Compound 패턴인 MVC 에서 Model 과 View 의 관계를 나타냅니다.

Controller 에 의해 Model 이 변경되면, Model 은 View 에게 갱신사실을 알려주죠. 

(위의 예제 역시 DataManager 를 일종의 Model로 볼 수 있으며, 데이터 갱신에 따라 View 에게 데이터를 알려줍니다.)


 


알게 모르게, Observer 패턴은 사실 많이 사용하고 있으며 매우 중요한 패턴이라고 할 수 있을 것 같습니다. 

[네트워크 비동기 처리, UI 이벤트 처리, MVC] 등 이 원리는 상당히 많이 적용하고 있으며, 꼭 숙지해야할 패턴이라는 생각이 드네요.. 


한번, 이 포스팅을 따라 꼭 실습을 해보는 것을 권장합니다. 


STUDY_OOP7.zip










반응형
Posted by N'

5. 실무 활용 패턴 (하).pdf



디자인 패턴과 관련된 마지막 STUDY 입니다.


모두들 수고하셨습니다. @.@ [꾸벅][꾸벅]


참고자료 

- 한국기술교육대학교 - 객체지향개발론및실습 (김상진 교수님)

- [Head First] Design Pattern

반응형
Posted by N'