Reactive Programming #0. 옵저버 패턴
목차
옵저버 패턴이란?
- 어떠한것들을 감지하고, 상태의 변경을 지켜보는 디자인 패턴.
- 여러개의 객체가 어떠한 특정 상태의 변경을 감지하고 반응을 해야하는 경우 적용.
- Pub/Sub 패턴을 쉽게 구현 가능.
채팅 서버/클라이언트 기반 예시 (옵저버 패턴 적용 전)
ChatServer 클래스
class ChatServer(
private val messages: MutableMap<String, MutableList<String>> = mutableMapOf()
) {
fun add(subject: String, message: String) {
messages[subject]?.let {
it.add(message)
}?: kotlin.run {
val messageList = mutableListOf<String>()
messageList.add(message)
messages.put(subject, messageList)
}
}
fun getMessage(subject: String): MutableList<String>? {
return messages[subject]
}
}
User 클래스
class User(
private val chatServer: ChatServer
) {
fun sendMessage(subject: String, message: String) {
chatServer.add(subject, message)
}
fun getMessage(subject: String): MutableList<String>? {
return chatServer.getMessage(subject)
}
}
Client 메인 함수
fun main() {
val chatServer = ChatServer()
val user1 = User(chatServer)
user1.sendMessage("디자인패턴", "이번엔 옵저버 패턴입니다.")
user1.sendMessage("롤드컵2021", "LCK 화이팅!")
val user2 = User(chatServer)
println(user2.getMessage("디자인패턴"))
user1.sendMessage("디자인패턴", "예제 코드 보는 중..")
println(user2.getMessage("디자인패턴"))
}
user1이 메세지를 보냈을때 user2는 계속해서 메세지를 가져와야 함.
위 경우에는 user2가 메세지를 받으려고 계속해서 요청을 보내는 형태. (Calling 매커니즘)
이러한 경우 메세지가 존재하면 좋겠지만 메세지가 존재하지 않을 경우도 있기 때문에 해당 애플리케이션에 맞지 않음.
-> 이럴때 옵저버 패턴을 사용한다.
옵저버 패턴 좀 더 자세하게!
다수의 객체가 특정 객체 상태 변화를 감지하고 알림 받는 패턴.
Subject
여러 옵저버들을 등록하거나 해지 할 수 있는 기능을 제공.
Subject의 상태가 변경되면 자신에게 등록 된 모든 옵저버를 순회하며 옵저버가 제공하는 모든 메서드를 호출.
채팅 서버/클라이언트 기반 예시 (옵저버 패턴 적용 후)
ChatServer 클래스 (Subject 역할)
class ChatServer(
private val _subscribers: MutableMap<String, MutableList<Subscriber>> = mutableMapOf()
) {
fun register(subject: String, subscriber: Subscriber) {
_subscribers[subject]?.let {
it.add(subscriber)
}?: kotlin.run {
val subscribers = mutableListOf<Subscriber>()
subscribers.add(subscriber)
_subscribers.put(subject, subscribers)
}
}
fun unregister(subject: String, subscriber: Subscriber) {
_subscribers[subject]?.let {
it.remove(subscriber)
}
}
fun sendMessage(user: User, subject: String, message: String) {
_subscribers[subject]?.forEach {
val receiver = it as? User
it.handleMessage("${user.name} -> ${receiver?.name} : $message")
}
}
}
Subscriber 인터페이스 (Observer 인터페이스 역할)
interface Subscriber {
fun handleMessage(message: String)
}
User 클래스 (ConcreteObserver 역할)
class User(
val name: String
): Subscriber {
override fun handleMessage(message: String) {
println(message)
}
}
Client 메인 함수
fun main() {
val chatServer = ChatServer()
val user1 = User("u1")
val user2 = User("u2")
chatServer.register("코틀린", user1)
chatServer.register("코틀린", user2)
chatServer.register("RX", user1)
chatServer.sendMessage(user1, "코틀린", "코틀린 꿀잼?")
chatServer.sendMessage(user1, "RX", "리액티브 스트림 어려워?")
}
옵저버 패턴의 장/단점
장점
- 상태를 변경하는 객체(publisher)와 변경을 감지하는 객체(subscriber)의 관계를 느슨하게 유지할 수 있음.
- Subject의 상태 변경을 주기적으로 조회하지 않고 자동으로 감지할 수 있음.
- 런타임에 옵저버를 추가하거나 제거할 수 있음.
단점
- 복잡도가 증가.
- 다수의 Observer 객체를 등록 이후 해지 하지 않으면 Memory Leak이 발생.
자바에 적용된 옵저버 패턴
- Java 9 이전
- Observer 인터페이스 (Java 9 부터 사용 권장하지 않음.)
- Observable 클래스 (Java 9 부터 사용 권장하지 않음.)
- Java 9 이후
- java.beans
- PropertyChangeListener
- PropertyChangeSupport
- Flow API (Reactive Stream을 구현할 수 있도록 제공되는 인터페이스)
- Subscription
- Subscriber
- Publisher
- 해당 인터페이스 만으로는 동기적으로 작동함. 비동기적으로 사용하려면 SubmissionPublisher와 같이 사용해야 함.
- 구독을 하는쪽에서 백프레셔를 제어 가능.
- java.beans
참고
https://www.inflearn.com/course/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4
'Programming' 카테고리의 다른 글
Reactive Programming #1. Reactive Streams (0) | 2022.01.21 |
---|