Contents
Observer PatternObserver Pattern
- 관찰자를 설정하여 해당 상태를 객체에게 알려주는 패턴
Observer vs Listener
- 특정 이벤트를 두고 Observer와 Listener의 동작이 다름
- Observer는 해당 이벤트가 발생했는지 탐지만 수행 : 이후 로직을 개발자가 작성해야 함
- Publisher → Observer → Subscriber의 구조 (중계)
- Listener는 해당 이벤트가 발생하면 지정된 동작을 수행
- Publisher(Source) → Listener의 구조 (직접 콜백)
Observer Pattern의 성질
- Observer가 해당 상태를 구독
- 구독 중인 상태가 변했을 경우, Observer는 Subscriber에게 두 가지 경우를 통해 상태를 전달
- Polling (폴링) : 상대의 요청이 있어야 전달 가능 (req - resp 통신, 통신이 끊김)
- Push (푸시) : Observer가 Subscriber에게 일방적인 resp 전송 (통신을 끊지 않음)
Ex. 마트 재고 구독
- 손님이 상품을 구매하기 위해서 마트의 재고를 구독
Q. 구독 / 출판 / 알림의 책임은 누구에게 있는가?
- 일반적으로 사용자가 해당 상태를 구독한다고 생각하지만, ‘구독’이라는 행위의 소유자는 출판사임
- 사용자는 해당 출판사의 구독을 호출함 → 출판사가 자신을 구독하도록 요청
- 구독, 출판, 알림은 모두 출판사가 담당함 : 사용자는 알림을 받는 역할만 수행


Polling
- Polling에서는 사용자가 해당 출판사에게 상태가 변경되었는지 확인해야 함 (req)
- 출판사는 사용자에게 해당 상태에 대한 응답을 전달 (resp)
- 그 결과 req-resp의 반이중 통신이 여러 번 진행되는 방식으로 진행
- 요청의 타이밍에 따라 서버의 성능에 영향을 주게 됨
- App.java
public class App {
public static void main(String[] args) {
Customer1 c1 = new Customer1();
LotteMart lotteMart = new LotteMart();
// 1. 마트는 입고 준비
new Thread(() -> {lotteMart.received();}).start();
// 2. 입고 확인
while(true) {
try {
Thread.sleep(100); // 요청하는 타이밍
String value = lotteMart.getValue();
if(value != null) {
c1.update(value + "이(가) 들어왔습니다.");
break;
} else {
System.out.println("상품이 준비되지 않았습니다.");
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
- LotteMart.java
public class LotteMart {
private String value = null;
public String getValue() {
return value;
}
public void received() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
value = "상품";
}
}
- Customer1.java
public class Customer1 {
public void update(String msg) {
System.out.println("손님 1이 받은 알림 : " + msg);
}
}
Push
- Push는 Polling과 다르게 해당 상태를 구독 중이면 통신을 끊지 않고 유지
- 상태가 변경되면 출판사가 직접 사용자에게 알림을 전송
- 반응형으로 작동하기 때문에 서버의 부담이 적음
- App.java
public class App {
public static void main(String[] args) {
LotteMart lotteMart = new LotteMart();
Mart emart = new EMart();
Cus1 c1 = new Cus1();
Cus2 c2 = new Cus2();
lotteMart.add(c1);
lotteMart.add(c2);
emart.add(c1);
emart.add(c2);
lotteMart.remove(c2);
// 출판 -> 알림 자동 : Callback
new Thread(() -> lotteMart.received()).start();
new Thread(() -> emart.received()).start();
}
}
- Mart.java
public interface Mart {
// 1. 구독
void add(Customer customer);
// 2. 구독 취소
void remove(Customer customer);
// 3. 출판
void received();
// 4. 알림
void notify(String msg);
}
- LotteMart.java
public class LotteMart implements Mart {
// 구독자 목록
private List<Customer> customers = new ArrayList<>();
// 구독 추가
@Override
public void add(Customer customer) {
customers.add(customer);
}
// 구독 제거
@Override
public void remove(Customer customer) {
customers.remove(customer);
}
// 출판
@Override
public void received() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
System.out.println(".");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
notify("LotteMart : 바나나");
}
// 알림
@Override
public void notify(String msg) {
for (Customer customer : customers) {
customer.update(msg);
}
}
}
- EMart.java
public class EMart implements Mart {
// 구독자 목록
private List<Customer> customers = new ArrayList<>();
// 구독 추가
@Override
public void add(Customer customer) {
customers.add(customer);
}
// 구독 제거
@Override
public void remove(Customer customer) {
customers.remove(customer);
}
// 출판
@Override
public void received() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
System.out.println(".");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
notify("EMart : 딸기");
}
// 알림
@Override
public void notify(String msg) {
for (Customer customer : customers) {
customer.update(msg);
}
}
}
- Customer.java
// Subscriber
public interface Customer {
void update(String msg);
}
- Cus1.java
public class Cus1 implements Customer {
@Override
public void update(String msg) {
System.out.println("손님 1이 받은 알림 : " + msg);
}
}
- Cus2.java
public class Cus2 implements Customer {
@Override
public void update(String msg) {
System.out.println("손님 2가 받은 알림 : " + msg);
}
}
Share article