유튜브의 경우 다음과 같이 작동한다.
1. 구독자는 유튜버를 구독한다
2. 유튜버가 영상을 만들어 구독자에게 영상을 보내준다
위와 같이 작동하기 때문에 나는 다른 일을 하고 있어도 유튜버에게 영상을 받아 볼 수 있다.
만약 과거의 서버-클라이언트 구조처럼 유튜브가 작동한다면 어떻게 작동할까?
1. 구독자는 유튜버에게 영상을 요청한다
2. 유튜버는 구독자에게 영상을 보내준다
이러한 방식에는 문제점이 있는데 만약 구독자가 다른일을 하고 있어 영상을 받아가지 못하는 경우 유튜버는 구독자를 계속 기다려야한다. 관찰자 패턴을 사용하면 이러한 문제점을 해결할 수 있다.
관찰자 패턴의 경우 다음과 같이 작동한다.
자바는 관찰자 패턴을 Observer(구독자) Observable(유튜버)를 통해 지원한다.
static class Youtuber extends Observable implements Runnable{
@Override
public void run() {
// 2. 유튜버는 새로운 영상을 만들어 구독자에게 알림을 보낸다.
System.out.println("새로운 영상 게시");
setChanged();
notifyObservers("알림");
}
}
public static void main(String[] args) {
Observer subscriber = new Observer() {
@Override
public void update(Observable o, Object arg) {
//3. 구독자는 알림을 확인하고 새로운 영상을 받아 신청한다.
System.out.println(arg.toString()+" 확인");
System.out.println("새로운 영상 시청");
}
};
Youtuber youtuber = new Youtuber();
//1. 구독자는 유튜버를 지켜본다.
youtuber.addObserver(subscriber);
youtuber.run();
}
실행 결과
하지만 위와 같은 옵저버 패턴에도 많은 문제점이 존재한다
1. 유튜버는 구독자에게 영상을 다 보내줬다고 알려줄 방법이 없다
2. 에러를 처리할 수 있게 제공해주는 표준화된 방법이 없어 에러를 처리하기 위해선 try-catch문을 통해 에러를 처리해줘야 한다
3. 유튜브가 모든 영상을 다 봐야만 새로운 영상을 받을 수 있다고 가정해보자
구독자는 새로운 영상을 다 못봤기 때문에 새로운 영상을 받을 수 없다
이런 문제점을 저장소를 만들어 저장소에 보관해 놓고 영상을 다 본 후 저장소에 있는 영상을 꺼내서 보는 방법으로 해결할 수도 있다. 하지만 저장소의 용량이 다 찼을때 유튜버가 새로운 영상을 보내면 새로운 영상은 유실되고말 것이다
이러한 문제점을 구독-발행 패턴을 통해 해결할 수 있다
관찰자 패턴과 구독-발행 패턴의 가장 큰 차이점은 중개자가 있다는 것이다.
1. 구독자는 유튜버를 구독한다
2. 유튜버는 중개자를 만들어 구독자에게 알려준다
3. 구독자는 중개자에게 영상을 얼마나 받을 수 있는지 알려준다
4. 유튜버는 영상을 만들어 게시한다
5. 중개자는 유튜버가 영상을 게시했으므로 영상을 가져와 구독자가 받을 수 있는 만큼 영상을 보내준다
자바는 구독-발행 모델을 Publisher와 Subscriber로 지원한다.
public static void main(String[] args) {
//유튜버
Publisher publisher = new Publisher() {
// 4. 유튜버는 영상을 게시한다.
Iterable<Integer> iter = Arrays.asList(1, 2, 3, 4, 5);
@Override
public void subscribe(Subscriber subscriber) {
Iterator<Integer> iterator = iter.iterator();
// 2. 유튜버는 중개자를 만들어 구독자에게 알려준다
subscriber.onSubscribe(new Subscription() {
@Override
public void request(long n) {
while (n-->0) {
if (iterator.hasNext()) {
// 5. 중개자는 구독자에게 영상을 보내준다.
subscriber.onNext(iterator.next());
} else {
subscriber.onComplete();
break;
}
}
}
@Override
public void cancel() {
}
});
}
};
//구독자
Subscriber<Integer> subscriber = new Subscriber<>() {
Subscription subscription;
@Override
public void onSubscribe(Subscription subscription) {
// 구독자는 중개자를 확인한다
this.subscription = subscription;
System.out.println("구독");
// 3. 구독자는 영상을 몇개 받을 수 있는지 중개자에게 알려준다
subscription.request(2);
}
@Override
public void onNext(Integer item) {
// 6. 구독자는 영상을 시청한다.
System.out.println(item+ " 영상 시청");
}
@Override
public void onError(Throwable throwable) {
System.out.println("onError");
}
@Override
public void onComplete() {
System.out.println("onComplete");
}
};
Subscriber<Integer> s = subscriber;
// 1. 구독자가 유튜버 구독한다
publisher.subscribe(subscriber);
}
실행 결과
구독자가 2개의 영상밖에 받을 수 없는 상태였으므로 2개의 영상만 출력된 것을 볼 수 있다. 받은 2개의 영상을 다 시청한 후에는 새로 중개자에게 요청을 하여 나머지 영상을 받아볼 수 있는 구조이다.
구독자가 받을 수 있는 만큼 영상을 건내줘 문제를 해결하는 것을 Back Pressure라고 한다.이렇게 발행자의 처리 속도가 구독자의 처리 속도보다 빠른 경우 문제가 생기는 것을 자바에서는 Subscription을 통해 해결한다.
또한 Publisher-Subscriber를 사용하면 onComplete()를 통해 완료를 통지할 수 있고 에러 또한 onError() 메소드를 통해 깔끔하게 처리가 가능하다.