일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- Hyperledger fabric gossip protocol
- 하이브리드앱
- Actor
- 그라파나
- 주키퍼
- 안드로이드 웹뷰
- 파이썬 데이터분석
- Golang
- Adapter 패턴
- 스칼라 강좌
- hyperledger fabric
- 스위프트
- 이더리움
- Play2 로 웹 개발
- 엔터프라이즈 블록체인
- Play2
- play 강좌
- Akka
- 블록체인
- 스칼라
- akka 강좌
- play2 강좌
- 하이퍼레저 패브릭
- 플레이프레임워크
- CORDA
- 파이썬 강좌
- 스칼라 동시성
- 파이썬 동시성
- 파이썬
- 파이썬 머신러닝
- Today
- Total
HAMA 블로그
굿바이~ 옵저버 패턴 and FRP 본문
문제 공유
우리는 오랫 동안 상호작용 되는 많은 부분에 있어서 옵저버패턴을 당연하듯 활용해 왔지만,
옵저버(관찰자, 소비자, 리스너) 패턴을 사용하다보면 경험 많은 개발자라면 누구나 "아 이거 먼가 깨름칙 한데" 라는 경험을 해보았을 것이다. 나 같은 평범한 개발자의 경우 그런 깨름칙한 냄새를 맡고서도, "내가 모자라서 그렇지 뭐" 자책을 하거나, "여기서 어떻게 더 잘 고칠수 있지? 옵저버패턴은 Gof 패턴 중 하나이며 훌륭한것이니 더 나은것은 없을 거야" 라고 이른 만족을 하거나, "그냥 잘 굴러가는 거 같아 보이니, 냅두자", "나는 코드를 잘 이해하고 있어, 다른 신참이나 이해 부족한 개발자 네 탓" 이 라고 기술 부채를 남기며 자기 최면을 건다든지 할 것이다.
하지만 역시 구루님들은 달랐다. 옵저버패턴의 문제점을 요목조목 따져가며 샅샅히 지적을 했으며, 그것에 대한 해결책까지 나왔다. 따라서 이젠 거인의 어깨에 올라 타서 그것에 관한 이해하고, 잘 만들어진 라이브러리를 사용하면 된다. 그 전반적인 내용에 대해서 설명 해 볼 예정이지만 모든 것을 쉽게 풀어서 적을 수는 없기에, 중간중간 이해 하기 힘든 구멍들은 각자 수고스럽지만 찾아보거나, 깊은 사색을 해야 할 것이다.
옵저버 패턴
어떤 상태를 관리하는 Subject 객체(상태머신,생산자,Observable)가 있고 , 이 객체에서 일어나는 상태 변화에 따라서 해당 이벤트를 받길 원하는 옵저버(관찰자,소비자)들은 매니져 객체에 자신을 등록 한다.
그 후에, Subject 객체에서 어떤 상태가 변경 되었을 때 , 자신에게 등록된 옵저버들에게 이벤트를 notify 해주는게 옵저버 패턴의 주요 골자이며, Subject 객체은 관찰자들에 대해서 유연하게 커플링 되어 있게 된다. 즉 매니저 객체가 소비자들에 대해서 정확한 정보를 알 필요가 없이, 소비자들이 알아서 Subject 객체가 선언한 인터페이스를 따라주고, 등록해주면 되므로, 매니저 객체는 독립적인 컴포넌트가 될 수 있는 여지가 생기는데 옵저버패턴은 그런 유연성이라는 장점을 내세우는 패턴이라고 할 수 있다.
자~ 그럼 옵저버패턴은 어디서 사용 될 까?
글쓴이 본인은 주로 그래픽스/편집기 솔루션을 개발한 경험이 많기 때문에 관련하여 설명 해본다. (이러한 곳에서 정말 많이 사용된다)
각종 도형의 리스트가 나열 되어 있는 리스트 박스가 있다. 우리는 그 리스트에 추가,삭제를 할 수 있는데, 도형 하나를 추가하면 (상태를 변경하면) 그 소식을 리스트의 데이터와 밀접한 관계를 가지는 다른 컴포넌트(객체) 들에게도 연락을 해줘야 한다. 만약 삼각형3을 추가해줬다면 아래와 같은 변경이 전파되어야 한다.
- 마우스 커서는 삼각형3에 해당하는 커서모양으로 변경
- 상태바에는 삼각형3가 추가되었다고 글씨가 추가
- 뷰화면에는 삼각형3에 해당하는 도형이 그려짐.
GUI 부분에서는 이것 말고도 매우 다양하게 사용되는데 다른 예는 마우스 버튼이 Subject 가 되며, 버튼이 클릭되는 상태변경 (좌클릭,우클릭,더블클릭등) 에 따라서 옵저버들이 고지 받아서 행동(선 그리기)하게 될 것이다.
웹이나 통신 개발에서의 예를 들면 아래 처럼 구성이 될 것이다.
MVC 패턴에서, M (model) 은 subject 역할을 맡고, V(view) 는 Observer 역할을 맡는 것도 유추 할 수 있다.
옵저버 패턴의 특징
디커플링 : 상태머신이 자신이 호출해 줘야 할 객체들을 모두 강하게 내부 변수로 가지고 있게 되면, 상태머신 자체를 다른 곳에서 재활용하기 힘들게 된다.
제어역전 : 옵저버 패턴은 전형적인 제어 역전 구조로 (사실 제어역전이란 말은 모호하기 때문에 그냥 DI 로 사용하는게 나음) 옵저버들이 상태머신을 계속 polling 하거나, 상태머신을 호출하는 것이아니라, 상태머신에 자신의 레퍼런스를 넘겨서 (addObserver , addListener , subscribe ) 상태머신에 의해 콜백되게 만든다.
옵저버 패턴의 문제
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)
먼저 리액티브라는 단어에는 다양한 의미가 있지만 가장 개발자로써 와닿을 수 있는 예시를 하나 보자.
{
a();
b():
c();
}
절차지향에 익숙한 우리는 위의 함수들이 순서대로 발생할 것이라는 것을 염두해 두코 코딩을 하게 된다.
즉 c() 함수는 a() 함수와 b() 함수가 무엇인가 처리를 한 후에 그것을 가지고 처리한다는 건데, 물론 순서가 중요하지 않을 때 도 있다. 이때 나중에 참여한 개발자가 저것을 다 파악하기란 힘들 것이다. (물론 소스가 복잡해 진 다면 말이죠) 또한 옵저버패턴이 사용된다면 그 순서란 더 감춰지기 마련이니 파악하기 힘들어 질 것이다.
의존 관계 와 순서는 이렇게 우리가 짜왔던 절차지향,객체지향에서는 혼동을 주게 되는데 최근에 코어를 더 적극적으로 활용해야하는 시대에 와서 멀티쓰레드라는 동시성을 다루는 부분에 있어서 더더욱 순서와 의존관계를 파악하기 힘들어 졌다.
FRP 와 Rx 의 반응형이라는 말에 뒤에는 이런 순서&의존관계를 명확하게 인지 시키준다는 의미도 포함되어 있으며 앞으로 FRP 와 Rx 를 공부하는데 있어서 이 점을 머리속에 넣어 두면 좋을 거 같다.
첨가) FRP와 Rx 시리즈들과는 다르다. Sodium FRP 저자가 작성한 내용을 참고하면
작성중....
레퍼런스:
https://infoscience.epfl.ch/record/148043/files/DeprecatingObserversTR2010.pdf
Functional Reactive Programming
https://www.scala-lang.org/
https://github.com/ReactiveX/RxJava
https://github.com/SodiumFRP/sodium
https://github.com/ReactiveX/RxPY
'소프트웨어 사색 ' 카테고리의 다른 글
블럭체인/비트코인 (1) | 2018.01.31 |
---|---|
소프트웨어는 유기물 (0) | 2018.01.15 |
Go 언어 / 객체지향은 반드시 없어져야 할 비용만 높은 재앙이다. (0) | 2017.07.22 |
알고리즘 논란은 알고리즘으로 치유를.. (0) | 2017.06.19 |
정적타입vs동적타입?? 단순한 언어가 최고!! (0) | 2017.06.07 |