배고픈 개발자 이야기
1.옵저버(Observer) 패턴 본문
옵저버(특정 목록의 변화 관찰)로 객체에 등록하여 객체가 직접 통지하도록 하는 패턴
-
주로 분산 이벤트 핸들링 시스템을 구현하는 데 사용된다. + 발행/구독 모델
1) 기본구성
어떤 상태를 관리하는 Subject 객체(상태머신,생산자,Observable)가 있고 , 이 객체에서 일어나는 상태 변화에 따라서 해당 이벤트를 받길 원하는 옵저버(관찰자,소비자)들은 매니져 객체에 자신을 등록 한다
그 후에, Subject 객체에서 어떤 상태가 변경 되었을 때 , 자신에게 등록된 옵저버들에게 이벤트를 notify 해주는게 옵저버 패턴의 주요 골자이며, Subject 객체는 관찰자들에 대해서 유연하게 커플링 되어 있게 된다. 즉 매니저 객체가 소비자들에 대해서 정확한 정보를 알 필요가 없이, 소비자들이 알아서 Subject 객체가 선언한 인터페이스를 따라주고, 등록해주면 되므로, 매니저 객체는 독립적인 컴포넌트가 될 수 있는 여지가 생기는데 옵저버패턴은 그런 유연성이라는 장점을 내세우는 패턴이라고 할 수 있다.
2) 그래픽스/편집기 솔루션 개발의 예
각종 도형의 리스트가 나열 되어 있는 리스트 박스가 있다. 우리는 그 리스트에 추가,삭제를 할 수 있는데, 도형 하나를 추가하면 (상태를 변경하면) 그 소식을 리스트의 데이터와 밀접한 관계를 가지는 다른 컴포넌트(객체) 들에게도 연락을 해줘야 한다. 만약 삼각형3을 추가해줬다면 아래와 같은 변경이 전파되어야 한다.
\- 마우스 커서는 삼각형3에 해당하는 커서모양으로 변경 \- 상태바에는 삼각형3가 추가되었다고 글씨가 추가 \- 뷰화면에는 삼각형3에 해당하는 도형이 그려짐.
GUI 부분에서는 이것 말고도 매우 다양하게 사용되는데 다른 예는 마우스 버튼이 Subject 가 되며, 버튼이 클릭되는 상태변경 (좌클릭,우클릭,더블클릭등) 에 따라서 옵저버들이 고지 받아서 행동(선 그리기)하게 될 것이다.
3) 웹이나 통신 개발에서의 예
MVC 패턴에서, M (model) 은 subject 역할을 맡고, V(view) 는 Observer 역할을 맡는 것도 유추 할 수 있다.
옵저버 패턴의 특
디커플링 : 상태머신이 자신이 호출해 줘야 할 객체들을 모두 강하게 내부 변수로 가지고 있게 되면, 상태머신 자체를 다른 곳에서 재활용하기 힘들게 된다.
![img](https://t1.daumcdn.net/cfile/tistory/99914033599641BE23)
제어역전 : 옵저버 패턴은 전형적인 제어 역전 구조로 (사실 제어역전이란 말은 모호하기 때문에 그냥 DI 로 사용하는게 나음) 옵저버들이 상태머신을 계속 polling 하거나, 상태머신을 호출하는 것이아니라, 상태머신에 자신의 레퍼런스를 넘겨서 (addObserver , addListener , subscribe ) 상태머신에 의해 콜백되게 만든다.
![img](https://t1.daumcdn.net/cfile/tistory/99D577335994F7C924)
옵저버 패턴의 문제
1. 예측 불가능한 순서
Subject (상태머신) 자체내에서는 옵저버들을 보통 리스트를 순회하며 Notify 를 해주게 되지만, 옵저버들의 추가,삭제가 자유로이 이루어지고 있고, 개별 옵저버 입장에서는 자신이 호출되는 순서에 대해서 알 수가 없다. 따라서 상태변경에 따른 행위를 할 때 , 자신의 순서 앞에서 어떤 다른 옵저버가 어떤 행위를 했는지를 미리 알 수가 없게 된다. 제어권을 Subject 로 제어역전을 시켜 준 결과 이렇게 되버리는데, 이것을 해결 하기 위해서는 옵저버들 전체를 일종의 트랜잭션으로 감싸서 트랙잭션이 끝날 때 어떤 행위를 하게 끔 하는 수 밖에 없지만, 코드복잡도가 상당히 올라가게 마련이다.
2. 첫번째 이벤트 소실
Subject(상태머신) 에 DI 를 해주는 시점이, Subject 에서 첫번째 이벤트(상태변경)가 발생하는 시점 보다 늦게 될 수가 있다. 예를들어 클라이언트가 접속되었다는 이벤트를 통지 받지 못한다면, 클라이언트와 상호통신하는 옵저버는 무용지물이 될 것이다.
3. 지저분한 상태
Subject(상태머신) 은 위에서 보았다시피, 계속 변경되기 때문에 그로 인한 부수효과(side-effects) 가 발생하기 마련이다. 상태가 1~2개라면 모르겠으나, 상태가 만약 5개이상을 가지고 있다고 하자. 그 상태를 변경하는 이벤트들의 종류가 10가지 라고하면, 50개의 조합이 생겨난다. 그런 조합에 의해 옵저버들이 호출 되었는데, 먼가가 작동을 안하는 버그가 생겼을 때 , 현재 상태가 올바른 상태인지에 대한 디버깅이 힘들어지기 마련이다.
4. 캡슐화 문제
옵저버패턴은 캡슐화를 종종 깨버리는데, 상태머신의 변경에 따라서 a 옵저버가 mylist 라는 변수를 초기화 시키고, b 옵저버는 mylist 라는 변수를 사용하게 되는 경우가 많이 있다.
5. 스레드 안전 문제
가장 곤혹스러운 문제이다. 일단 Subject (상태머신) 내에서도 락이나 경쟁관계를 해소해야하지만, 그것 보다 더 큰 문제는 각각의 옵저버들과 그 옵저버들이 호출하는 함수체인 속에서 어떤 락을 잡고 있는지 알 수 없게 되는 경향이 있다. 즉 A 옵저버가 a 락을 잡고 b 락을 잡으려고 하지만, B 옵저버가 이미 b락을 잡고 있다면 (a락을 잡아야 b락을 풀어준다면) 터지는거다. 역시 상태를 가지고있는 모든 OOP 프로그래밍에서의 쓰레드 사용은 큰 문제거리가 될 수 밖에 없을 것이다. 더군다나 옵저버패턴처럼 제어권이 역전 된 상태이면 더더욱~
6. 콜백 누수
가장 옵저버(리스너)를 등록 시킨 후에, 쓸모가 없어 졌을때 removeObserver, removeListener, dipose 등을 호출하는 것을 잊어 버렸다고 하자. 앞으로 상태가 변경 될 때 마다 쓸때없는 CPU 사이클만 날려버릴 것이다. 옵저버 패턴이 생산자와 소비자의 제어 관계를 자연스럽게 역전 시켜서 생산자가 소비자에게 의존 하지 못하게 만드는 것이지만, 생산자의 실수를 소비자는 알 수가 없게 된다. 이상적이라면 이 관계를 다시 역전 시켜야 한다.
7. 의도치 않은 재귀
실제 현업에서 복잡한 솔루션을 짤 때, 이 문제도 쓰레드 문제와 같이 가장 크게 다가오곤 한다. 예를들어 커맨드 패턴에서 execute 가 발생해서 -> 상태머신(S) 의 상태를 변경하면 -> 옵저버 A가 호출되고 -> 옵저버 B가 호출되고 -> ... -> 옵저버A 는 어떤 다른 함수를 호출하고 그 함수는 -> 무엇인가를 하고 -> 여기서 끝나야 하지만 -> 마지막 함수는 다시 상태머신(S) 를 변경한다.
바보라고 말 할 수 있겠지만, 정말 복잡한 수백만 라인의 코드에서는 종종 일어나는 일이다. 자신이 코드의 모든 곳을 속속들이 알지 못하는 신참일 경우, 일 부분에 대해서만 작업을 하게 되는데 자신의 작업이 가져 올 여파까지는 미리 알 수가 없는 상황인 경우가 발생한다.
8. 기타등등
Composability, SOC , Scalabilty, Abstraction, Resource management, Semantic distance 등이 있으며. 아래 레퍼런스 중 첫번째 마틴오더스키의 논문에 짧은 코멘트가 적혀져 있다.
FRP(Functional Reactive Programming) 로 모든 문제를 해결할 수 있다.
순서&의존관계 (코어, 멀티쓰레드)활용시 파악이 힘듬, FRP로 이런 관계를 명확하게 인지하게 해준다는 개념이 있음(+Rx)
옵저버 패턴 : 테스트 코드
from abc import ABCMeta, abstractmethod
class Subject:
__metaclass__ = ABCMeta
@abstractmethod
def register_observer(self):
pass
@abstractmethod
def remove_observer(self):
pass
@abstractmethod
def notify_observers(self):
pass
class Observer:
@abstractmethod
def update(self, temperature, humidity, pressure):
pass
@abstractmethod
def register_subject(self, subject):
pass
class weejiwon(Subject):
def __init__(self):
super(weejiwon, self).__init__()
self._observer_list = []
self.happiness = 0
self.sadness = 0
def register_observer(self, observer):
if observer in self._observer_list:
return "Already exist observer!"
self._observer_list.append(observer)
return "Success register!"
def remove_observer(self, observer):
if observer in self._observer_list:
self._observer_list.remove(observer)
return "Success remove!"
return "observer does not exist."
def notify_observers(self): #옵저버에게 알리는 부분 (옵저버리스트에 있는 모든 옵저버들의 업데이트 메서드 실행)
for observer in self._observer_list:
observer.update(self.happiness,self.sadness)
def emotionalChanged(self):
self.notify_observers() #감정이 변하면 옵저버에게 알립니다.
def set_emotional(self, happiness,sadness):
self.happiness=happiness
self.sadness=sadness
self.emotionalChanged()
class Emotion(Observer):
def update(self, happiness,sadness): #업데이트 메서드가 실행되면 변화된 감정내용을 화면에 출력해줍니다
self.happiness=happiness
self.sadness=sadness
self.display()
def register_subject(self, subject):
self.subject = subject
self.subject.register_observer(self)
def display(self):
print ('weejiwon Emotion happiness:',self.happiness,' sadness:',self.sadness)
def test():
weejiwonObj = weejiwon()
EmotionObj=Emotion()
EmotionObj.register_subject(weejiwonObj)
weejiwonObj.set_emotional('good','good')
weejiwonObj.set_emotional('Not good','Not good')
weejiwonObj.set_emotional('Bad','Bad')
if __name__ == '__main__':
test()
'전산학 > 디자인패턴' 카테고리의 다른 글
4.싱글톤 (Singleton) 패턴 (0) | 2019.09.14 |
---|---|
3.팩토리 메서드 (Factory method) 패턴 (0) | 2019.09.14 |
2.커맨드 (Command) 패턴 (0) | 2019.09.14 |
0.스트래티지 (strategy pattern) 패턴 (0) | 2019.09.08 |