Observer 패턴, 많이 들어봤다. GoF의 정의로 알아보자.
Observer
- 관찰자, 감시자
- 변화가 생기면 알아챈다.
- 다만 이 관찰자는 여러명일 수 있다.
pub-sub Pattern
- Observer보다 많이 쓰이는 패턴
- 발행-구독 패턴
- 비슷하나 엄밀히 말하면 다른 패턴이다.
- 하지만 이루려는 목적은 비슷하여 같은 패턴이라 보는 일도 많다.
- pub-sub이 보다 일반적인 (observer의 상위 집합) 개념이라 생각
LogManager
- 바로 전에 봤던 LogManager가 그 패턴
- program이 LogManager에게 로그 메시지를 보내고,
- LogManager는 구독자(ConsoleLogger, EmailLogger)들에게 메시지를 보냄
Observer Pattern의 예
- 크라우드펀딩
- 돈이 들어올 때마다 두 개체를 업데이트할 것임
- 장부 (금액)
- 폰에서 푸시 노티 (이름, 금액)
- 실제로 푸시 알림이 pub-sub 패턴임
- 이를 이벤트 주도 아키텍처라고도 한다.
- 이벤트가 일어나면 알려줌
IFundingCallback
public interface IFundingCallback {
void onMoneyRaised(String backer, int amount);
}- 동일한 인터페이스로 받기 위해 만듦
BookkeepingApp
public final class BookkeepingApp implements IFundingCallback {
// ... 생략
@Override
public void onMoneyRaised(String backer, int amount) {
// 장부에 새 내역 추가
// amount만 사용
}
}MobileApp
public final class MobileApp implements IFundingCallback {
@Override
public void onMoneyRaised(String backer, int amount) {
// 모바일 앱에 알림 보여줌
// amount, backer 둘다 사용
}
}CrowdFundingAccount
public final class CrowdFundingAccount {
private int balance;
private ArrayList<IFundingCallback> subscribers;
public CrowdFundingAccount() {
this.subscrivers = new ArrayList<IFundingCallback>();
}
public void subscribe(IFundingCallBack sub) {
subscrivers.add(sub);
}
public void support(String backer, int amound) {
this.balance += amount;
for (IFundingCallback sub : subscribers) {
sub.onMoneyRaised(backer, amount);
}
}
}pub-sub과의 차이
- 다대다인 관계라면 pub-sub이라 할 수 있다.
- 지금 위의 경우 발행자가 하나다.
- 그런데 만약 발행자가 여러개가 될 수 있고, 그걸 받는 쪽도 여러개라고 해보자.
- 이럴 경우 그 중간에 조율해주는 개체가 있어야 한다.
- 그 차이가 전부다. 위같은 경우는 Account에 직접적으로 빨대를 꽂고 있는 형태니 Observer이다.
- pub-sub이라면, support()를 호출하면, 중간 관리 개체에 요청하고, 그 개체가 구독자들에게 다시 알림을 주는 형태가 될 것이다.
- iOS의 NotificationCenter와 같은 방식이다.
Observer Pattern과 메모리 누수 문제
- Observer Pattern은 결국 콜백 함수의 목록이다.
- 그런데 이런 방식은 Managed 언어에서 메모리 누수를 만드는 주범이다.
- 메모리 직접 관리
- 가비지 컬렉터 기반 언어
- 메모리 누수가 안생기는게 아님
- 메모리 누수가 발생하는 방법이 다른 것
CrowdFundingAccount funding;
BookkeepingApp book = new BookkeepingApp();
funding.subscribe(book);
// 장부 앱으로 무언가 처리
// 다함, 이제 개체 지우자.
book = null;
// 시간이 많이 지남 - 가비지 컬렉터 한번 돎
// 하지만 안지워짐 응? 왜?- funding 개체가 해당 메모리 주소를 잡고 있기 때문에, 사용중이라고 생각함
subscribers
- 명시적으로
unsubscribe()와 같은 동작으로 배열에서 빼줘야 함
public void unsubscribe(IFundingCallback sub) {
subscribers.remove(sub);
}- 이거 안까먹을 자신 있음?…
- 여기서 찾기도 굉장히 어렵다..
- Java의 경우에는 book이 지워질 때 자동으로
unsubscribe()를 호출하게 만들기도 어렵다.- C++에는
destructor를 통해 자동화할 수 있다. - Swift
deinit()
- C++에는
- 또 C++은 굳이 heap에 안만들고 stack에 만들 수도 있다.
- 이렇게 되면 함수 스코프 벗어나면 무조건적으로 메모리 할당 해제 시킬 수 있다.
- stack이 그렇게 도니까!
- 이렇게 되면 함수 스코프 벗어나면 무조건적으로 메모리 할당 해제 시킬 수 있다.