[디자인 패턴] 8 옵저버 패턴(Observer Pattern)

류재성's avatar
May 29, 2024
[디자인 패턴] 8 옵저버 패턴(Observer Pattern)
 

1. 옵저버 패턴(Observer Pattern)이란?

💡
옵저버 패턴(Observer Pattern)은 한 객체의 상태가 변경될 때 그 객체에 의존하는 다른 객체들에게 자동으로 알림이 가고, 그에 따라 업데이트가 이루어지는 방식이다. 이 패턴은 일대다(one-to-many) 의존성을 정의하며, 주로 이벤트 핸들링 시스템이나 모델-뷰-컨트롤러(MVC) 아키텍처에서 많이 사용된다.
 
경우 1
notion image
 
손님이 마트에 바나나가 있는지 물어보고 응답하는 상황. 이 경우는 바나나가 입고되는지 확인하는 직원과 입고되면 손님에게 알려주는 직원 두 명이 필요하다. 단점은 손님이 많아지게 되면 알림을 주는 점원의 수가 많아져야 한다. 이 점원은 자바에서 스레드이다.
 
경우 2
notion image
 
두 번째 경우는 손님이 마트를 구독하는 경우이다. 손님이 마트를 구독하면 점원은 손님의 정보를 메모장에 기록한 후, 입고 완료되면 메모장에 기록된 모든 손님에게 알림을 보내는 방식이다.
 
이런 경우를 비동기 처리라고 하며, 메모장은 이벤트 루프라고 한다.
 
이때 손님이 구독을 하면 물건이 입고 됐을 때 자동으로 알림이 실행되는 형태를 옵저버 패턴이라고 한다.
 

2. 폴링(Polling)

💡
폴링(Polling)은 주기적으로 어떤 상태나 조건을 확인하는 기법을 의미한다. 이는 특정 이벤트가 발생했는지, 특정 조건이 충족되었는지를 확인하기 위해 일정한 간격으로 반복적으로 상태를 체크하는 방식이다.
 
package ex08.polling; public class LotteMart { private String value = null ; public String getValue(){ return value; } public void reveived(){ for (int i = 0 ; i < 5 ; i++){ try{ Thread.sleep(1000); }catch (InterruptedException e){ throw new RuntimeException(); } } value = "상품"; } }
 
마트 클래스를 만든다. 마트는 5초 후에 물건이 입고된다.
 
package ex08.polling; public class Customer1 { public void update(String msg){ System.out.println("손님1이 받은 알림 : "+msg); } }
 
package ex08.polling; public class App { public static void main(String[] args) { LotteMart lotteMart = new LotteMart(); Customer1 customer1 = new Customer1(); // 1 . 마트 입고 준비 new Thread(() -> { lotteMart.reveived(); }).start(); // 2. 손님1 while (true){ // 반복하지 않으면 비동기로 진행되기 떄문에 값이 차기 전에 null 로 끝난다. String call = lotteMart.getValue(); System.out.println(call); if(call!=null) break; } } }
notion image
 
입고 상태를 확인하기 위해 계속 통신을 한다.
 
💡
폴링 방식에서 가장 중요한건 요청을 하는 타이밍이다. 너무 자주하면 서버에 부하가 생기고, 주기가 길어지면 실시간으로 변화를 확인할 수 없다. 만드는 비지니스 조건에 따라 주기를 정해야 한다.
 
 
package ex08.polling; public class App { public static void main(String[] args) { LotteMart lotteMart = new LotteMart(); Customer1 customer1 = new Customer1(); // 1 . 마트 입고 준비 new Thread(() -> { lotteMart.reveived(); }).start(); // 2. 손님1 while (true){ // 반복하지 않으면 비동기로 진행되기 떄문에 값이 차기 전에 null 로 끝난다. // 폴링의 핵심. 통신 주기 try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } if (lotteMart.getValue()!=null){ customer1.update(lotteMart.getValue()+"가 들어왔습니다."); }else { System.out.println("상품이 아직 들어오지 않았습니다."); } } } }
 
notion image
 
💡
응답을 받고 통신이 종료되지 않고 계속 응답받는걸 플럭스라고 한다.
 

2. 푸쉬(Push)

💡
푸쉬(Push) 방식은 생산자가 데이터를 능동적으로 소비자에게 전달하는 방식이다. 생산자가 새로운 데이터를 생성하거나 이벤트가 발생할 때마다 소비자에게 즉시 알림을 보낼 수 있다.
 
notion image
 
손님이 마트에 구독을 하면 메모장에 적어놨다가 입고가 되면 즉시 알림을 보낸다.
 
package ex08.push.pub; import ex08.push.sub.Customer; public interface Mart { //1. 구독자 등록 void subscribe(Customer customer); //2. 출판(상품 관찰) void receive(); //3. 알림(구독자에게 알림) void notify(String msg); //4. 구독 취소 void remove(Customer customer); }
 
우선 인터페이스 Mart 클래스를 만든다. 인터페이스에는 구독, 출판, 알림, 구독취소 메서드가 있다.
 
package ex08.push.sub; public interface Customer { void update(String msg); }
 
손님은 업데이트 메서드를 가지고 있다. 이 메서드를 통해 손님은 마트로부터 알림을 받을 수 있다.
 
 
package ex08.push.pub; import ex08.push.sub.Customer; import java.util.ArrayList; import java.util.List; public class LotteMart implements Mart { private List<Customer> customerList = new ArrayList<>(); // 구독자 명단을 여기 담는다. @Override public void subscribe(Customer customer) { customerList.add(customer); } @Override public void receive() { for (int i = 0; i < 5; i++) { System.out.println("."); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } //알림 notify("롯데마트 : 바나나"); } @Override public void notify(String msg) { customerList.forEach(customer -> { customer.update(msg); }); } @Override public void remove(Customer customer) { customerList.remove(customer); } }
 
인터페이스를 구현한 LotteMart 클래스를 만든다.
 
private List<Customer> customerList = new ArrayList<>();
 
💡
빈 배열을 만들어 구독한 손님의 정보를 리스트에 담는다. 그리고 receive()가 호출되면 notify()메서드도 호출되고, Customer 의 update 메서드도 같이 호출된다. 이를 옵저버 패턴이라고 한다.
 
 
package ex08; import ex08.push.pub.LotteMart; import ex08.push.pub.Mart; import ex08.push.sub.Customer; import ex08.push.sub.Customer1; import ex08.push.sub.Customer2; public class App { public static void main(String[] args) { // 1. bean 로드 Mart lotteMart = new LotteMart(); Customer customer1 = new Customer1(); Customer customer2 = new Customer2(); // 2. 구독 lotteMart.subscribe(customer1); lotteMart.subscribe(customer2); // 3. 구독취소 lotteMart.remove(customer2); // 4. 출판 new Thread(() -> { lotteMart.receive(); }).start(); // 구독자는 구독만 함. 업데이트는 출판을 했더니 자동으로 update가 호출됨. 콜백 } }
 
notion image
 
구독 후 출판이 완료되면 자동으로 손님에게 알림이 간다.
Share article
RSSPowered by inblog