시작하기
간간이 패턴 공부를 했지만 Observer패턴은 처음 들어봤다.😂
채팅 서버를 한번 구축해 보고 싶다는 생각에 서치하던 중 Spring Webflux, RxJava를 접하게 되었고
Webflux로 채팅서버를 만들어보면 좋겠다는 생각에 공식문서나 기술문서를 이리저리 찾아봤는데 "발행자, 구독자"라는 키워드가 무진장 나오면서 Observer패턴이 등장했다.
걍 무시하고 Webflux지식을 줍줍 할랬는데.....
도통 뭔말인지 이해가 되질 않아 기초인 Observer패턴을 공부하게 되었다.
패턴을 공부한다고 Webflux를 자세히 알지는 못하겠지만 "어느정도 플로우는 알겠지!"라는 느낌적인 느낌으로 공부해 본다. 레고레고!
개념
먼저 위키백과에서 설명하는 옵저버 패턴의 개념을 읽어보자.
(습관처럼 위키백과를 먼저 찾는 1인)
옵서버 패턴 (observer pattern)은 객체 의 상태 변화를 관찰하는 관찰자들, 즉 옵저버들의 목록을 객체에 등록하여 상태 변화가 있을 때마다 메서드 등을 통해 객체가 직접 목록의 각 옵저버에게 통지하도록 하는 디자인 패턴 이다. 주로 분산 이벤트 핸들링 시스템을 구현하는 데 사용된다. 발행/구독 모델 로 알려져 있기도 하다.
위키백과
웬일로 위키에서 나름 쉽게 설명해놨다.
위키에서 정의한 개념 중 에서 “발행/구독”을 주의깊게 보자.
옵저버 패턴에서는 발행자(publisher)와 구독자(subscriber)가 있고 발행자의 상태 변화(발행물 발행)에 따라 구독자에게 통보하는 디자인 패턴이다.
대표적으로 유튜브를 생각해보자.
일반 유튜브 사용자(subscriber)는 특정 유튜버(publisher)를 구독(subscribe)하면 유튜버의 영상이 발행될때 마다 알림을 받을 수 있다.
여기서 일반 유튜브 사용자는 구독자가 되고 영상을 올리는 유튜버는 발행자가 된다.
단순하게 생각하면 옵저버 패턴은 유튜브 사용자와 유튜버의 관계를 뜻 한다.
(실제 코드로 옮기면 단순하지 않지만...😅)
어느정도 개념은 이해했으니 예제와 다이어그램을 보면서 실전으로 들어가 보자.
다이어그램
👉 NumberGenerator
구독자를 등록, 삭제, 알림 발행자 역할을 담당한다.
추상클래스로써 구독자를 등록, 삭제, 알림 기능을 담당하는 역학을 하며, 발행자의 상태값, 상태변화는 NumberGenerator를 extends한 Class에서 담당 한다.
👉 RandomNumberGenerator
NumberGenerator을 extends한 Class며, 실제 발행자가 발행하고 발행물(상태값) 관리를 담당하는 역할을 한다.
NumberGenerator는 발행자의 기본틀을 만들었다면 RandomNumberGenerator는 기본틀을 가져와 실질적이고 구체적인 행동을 한다.
예제에서 랜덤 숫자(발행물)을 발행하는 발행자 역할을 한다.
👉 Observer
interface로써 구독자의 기본틀을 구성한다.
Observer interface를 implement를 받은 class는 update메소드를 구현해야 되며, update메소드는 발행자가 발행과 동시에 구독자는 감지하여 구독자 마다 맞는 행동을 한다.
👉 DigitObserver, GraphObserver
실제 구독자 역할을 담당하며, 발행자의 발행물을 감지하여 구독을 한다.
update메소드가 구독의 행동을 정의한 메소드다.
대충 느낌 잡았으니 이제 예제를 통해 자세히 알아보자.
예제
Main Class
package com.example.mvc;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MvcApplication {
public static void main(String[] args) {
SpringApplication.run(MvcApplication.class, args);
// 발행자 생성
NumberGenerator generator = new RandomNumberGenerator();
// 구독(구독자 등록)
Observer observer1 = new DigitObserver();
Observer observer2 = new GraphObserver();
generator.addObserver(observer1);
generator.addObserver(observer2);
// 발행(발행자 상태값 변경)
generator.execute();
}
}
👉 Main Class
- 시작점을 Main Class에서 잡자. 발행자를 생성하고 구독자를 발행자에 저장 후(구독하기) 발행자의 상태를 변경 하는 일련의 과정이 이루어진다.
👉 NumberGenerator generator = new RandomNumberGenerator()
- 먼저 발행자를 생성하자.
- RandomNumberGenerator가 실제 발행자 역할을 담당하고 있다.
- NumberGenerator는 추상클래스로써 구독자 등록, 삭제, 알림 역할을 한다.
👉 Observer observer1 = new DigitObserver()
Observer observer2 = new GraphObserver()
generator.addObserver(observer1)
generator.addObserver(observer2)
- DigitObserver구독자와 GraphObserver구독자는 RandomNumberGenerator발행자를 구독한다.
- 정확하게 말하면 “Digit구독자와 Graph구독자를 RandomNumberGenerator에 저장한다”는 의미가 명확하다.
👉 generator.execute()
- RandomNumberGenerator발행자가 발행하는 단계다.
- 해당 발행 단계가 진행되면 발행자의 상태값은 변경이 이루어지고 구독자(Digit, Graph)에게 변경된 내용이 전달 된다. 자세한 내용은 해당 클래스에서 확인하자.
NumberGenerator Class
package com.example.mvc;
import java.util.ArrayList;
import java.util.Iterator;
public abstract class NumberGenerator {
private ArrayList observers = new ArrayList();
public void addObserver(Observer observer) {
observers.add(observer);
}
public void deleteObserver(Observer observer) {
observers.remove(observer);
}
public void notifyObservers() {
Iterator it = observers.iterator();
while(it.hasNext()) {
Observer o = (Observer)it.next();
o.update(this);
}
}
public abstract int getNumber();
public abstract void execute();
}
👉 private ArrayList observers = new ArrayList()
- 등록된 구독자 정보가 담겨있다.
👉 public void addObserver(Observer observer) { ... }
- 구독자를 등록한다.
- Observer는 interface를 사용하여 DigitObserver와 GraphObserver를 모두 받을 수 있게 한다.
👉 public void deleteObserver(Observer observer) { ... }
- 구독자를 삭제한다.
- Observer는 interface를 사용하여 DigitObserver와 GraphObserver를 모두 받을 수 있게 한다.
👉 public void notifyObservers() { ... }
- 등록된 Observer(구독자)에게 발행자의 변경된 상태값(발행자가 발행한 내용)을 알린다.
- 다르게 말하면 Main Class에서 NumberGenerator에 저장한 Observer(DigitObserver, GraphObserver)를 iterator로 한개씩 불러와 Observer의 update메소드를 실행시킨다.
- 즉, 구독자와 발행자 관점에서는 “발행자가 구독자에게 알린다”고 표현할 수 있지만 작성된 코드 관점에서는 “구독자 스스로가 아닌 발행자가 구독자의 메소드(update)를 실행하여 구독자가 알게된다”고 표현 할 수 있다.
RandomNumberGenerator Class
package com.example.mvc;
import java.util.Random;
public class RandomNumberGenerator extends NumberGenerator {
private Random random = new Random();
private int number;
public int getNumber() {
return number;
}
public void execute() {
for(int i = 0; i < 3; i++) {
number = random.nextInt(10);
notifyObservers();
}
}
}
👉 public void execute() { ... }
notifyObservers()
- 랜덤숫자(발행물)을 생성 후 notifyObservers()를 통해 구독자에게 발행물을 알린다.
- notifyObservers()는 NumberGenerator에 정의 되어있으며, observer를 담당하는 NumberGenerator에서 구독자에게 알린다.
Observer Class
package com.example.mvc;
public interface Observer {
public abstract void update(NumberGenerator generator);
}
👉 update(NumberGenerator generator)
- 구독자들의 메소드의 기본틀을 만든 interface다.
- 실제 구독자들은 Observer inertface를 implements를 받아 공통된 메소드를 정의한다.
GraphObserver Class
package com.example.mvc;
public class GraphObserver implements Observer{
@Override
public void update(NumberGenerator generator) {
System.out.println("=== GraphObserver Start ===");
int count = generator.getNumber();
for(int i = 0; i < count; i++) {
System.out.println("\*");
}
System.out.println("=== GraphObserver End ===");
}
}
👉 update(NumberGenerator generator) { ... }
- 실제 구독자중 하나이며, 발행물 알림을 받고 실제 동작을 정의한다.
- GraphObserver는 발행물(랜덤숫자)값 만큼 *를 찍어내는 역할을 한다.
DigitObserver Class
package com.example.mvc;
public class DigitObserver implements Observer {
@Override
public void update(NumberGenerator generator) {
System.out.println("<<< DigitObserver Start >>>");
System.out.println(generator.getNumber());
System.out.println("<<< DigitObserver End >>>");
}
}
👉 update(NumberGenerator generator) { ... }
- GraphObserver와 동일하게 실제 구독자중 하나다.
- 발행물(랜덤숫자)값를 출력하는 역할을 한다.
언제 사용할까?
위키백과에서 아래와 같은 경우에 옵저버 패턴을 대표적으로 사용한다고 한다.
- 외부에서 발생한 이벤트(사용자 입력같은)에 대한 응답(이벤트 기반 프로그래밍)
- 객체의 속성 값 변화에 따른 응답.
개인적으로 Observer패턴을 사용한적은 없다.
다만, 앞에서 말했듯 RxJava, Webflux, Project Reactive의 기초가 Observer패턴이라 공부만 한 정도다.
실제로 Observer패턴을 적용하는 경우가 생기면 블로그를 업데이트 하겠다!
장점 단점
장점
- 느슨한 결합
- 실시간으로 객체의 변경사항을 다른 객체로 전파가 쌉가능
단점
- 많이 사용하면 코드의 복잡성과 상태관리가 힘들어진다.
실제로 사용해본적 없어 장점과 단점은 다른 블로그와 책 내용을 참고 했다.(완전솔직)
장점 과 단점도 실제로 Observer패턴을 적용하는 경우가 생기면 블로그를 업데이트 하겠다!
마무리
Observer패턴의 기본개념, 다이어그램, 기초예제 까지 실제 사용을 위함보다는 개념을 중점으로 알아봤다.
생각보다 많이 Observer패턴을 기본 베이스로 활용한 곳이 많았다.
비록 Webflux라는 목표를 달성하기 위해 수단으로 Observer패턴을 공부하긴 했지만 다양한 곳에서 많이 사용하는 패턴이니 꼭 한번 실제 프로젝트에 적용해 보고 싶다.
잘못된 내용이나 보충이 필요한 내용이 있다면 댓글 남겨 주세요!
주니어 개발자에게 큰 도움이 됩니다! 감사합니다! 😃
Reference
'개발노트 > Spring' 카테고리의 다른 글
[디자인 패턴] Singleton Pattern (0) | 2022.03.27 |
---|---|
[Spring Boot] Jackson 기본 개념과 LocalDateTime변환 이슈 (0) | 2022.03.21 |
[Spring Boot] Rest API 에 Spring Security Form 로그인 적용하기 (4) | 2021.12.27 |
[Spring Boot] 파일 다운로드 - 서버에서 다운 (1) | 2021.08.16 |
[Spring Boot] @Vaild Annotation - 유효성 검사 (0) | 2020.08.23 |
개발 기록
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!