옵저버 (Observer) 패턴
다수의 객체가 특정 객체 상태 변화를 감지하고 알림을 받는 패턴
발행(publish) / 구독(subscribe) 패턴을 구현할 수 있다.
옵저버 (Observer) 패턴 before
Chat Server
public class ChatServer {
private Map<String, List<String>> messages;
public ChatServer() {
this.messages = new HashMap<>();
}
public void add(String subject, String message){
if(messages.containsKey(subject)){
messages.get(subject).add(message);
return;
}
List<String> messageList = new ArrayList<>();
messageList.add(message);
messages.put(subject, messageList);
}
public List<String> getMessage(String subject){
return messages.get(subject);
}
}
User
public class User {
private ChatServer chatServer;
public User(ChatServer chatServer){
this.chatServer = chatServer;
}
public void sendMessage(String subject, String message){
chatServer.add(subject, message);
}
public List<String> getMessage(String subject){
return chatServer.getMessage(subject);
}
}
Client
public class Client {
public static void main(String[] args) {
ChatServer chatServer = new ChatServer();
User user1 = new User(chatServer);
User user2 = new User(chatServer);
user1.sendMessage("디자인 패턴", "옵저버 패턴~");
user1.sendMessage("오늘 날씨", "맑음");
System.out.println(user2.getMessage("오늘 날씨"));
user1.sendMessage("디자인 패턴", "싱글톤 패턴 어렵다");
System.out.println(user2.getMessage("디자인 패턴"));
}
}
// 출력
[맑음]
[옵저버 패턴~, 싱글톤 패턴 어렵다]
간단한 채팅 서버와 사용자 예제 코드이다.
현재 코드에 어떤 문제점이 있을까?
사용자는 채팅 서버에 대해 강한 결합을 가진다.
사용자는 내부에 채팅 서버를 가진다.
즉 채팅 서버에 변경이 일어나면 사용자 또한 변경이 일어난다.
사용자는 업데이트된 메시지를 실시간으로 가져올 수 없다
getMessage() 메서드를 호출해야만 새로운 메시지를 가져올 수 있다.
이는 수동적인 방식으로, 새로운 메시지가 있을 때마다 사용자가 직접 서버를 조회해야 하므로 비효율적이다.
옵저버 (Observer) 패턴 after
Observer 인터페이스 - Subscriber
public interface Subscriber {
void handleMessage(String message);
}
Concrete Observer - User
@Getter
public class User implements Subscriber{
private String name;
public User(String name) {
this.name = name;
}
@Override
public void handleMessage(String message) {
System.out.println(message);
}
}
Subject - Chat Server
public class ChatServer {
private Map<String, List<Subscriber>> subscribers;
public ChatServer() {
subscribers = new HashMap<>();
}
public void register(String subject, Subscriber subscriber){
if(subscribers.containsKey(subject)){
subscribers.get(subject).add(subscriber);
return;
}
List<Subscriber> list = new ArrayList<>();
list.add(subscriber);
subscribers.put(subject, list);
}
public void unregister(String subject, Subscriber subscriber){
if (subscribers.containsKey(subject)) {
subscribers.get(subject).remove(subscriber);
}
}
public void sendMessage(User user, String subject, String message){
if(subscribers.containsKey(subject)){
String userMessage = user.getName() + ": " + message;
this.subscribers.get(subject).forEach(s -> s.handleMessage(userMessage));
}
}
}
Client
public class Client {
public static void main(String[] args) {
ChatServer chatServer = new ChatServer();
User user1 = new User("jeu1");
User user2 = new User("jeu2");
chatServer.register("롤드컵", user1);
chatServer.register("롤드컵", user2);
chatServer.register("디자인패턴", user1);
chatServer.sendMessage(user1, "롤드컵", "제우스");
chatServer.sendMessage(user2, "디자인패턴", "옵저버 패턴 ~~");
}
}
이제 직접 조회하지 않아도 메시지를 보내면 구독자들에게 메시지를 전달한다.
또한 사용자가 직접 ChatServer에 의존하지 않아 느슨한 결합을 실현했다.
옵저버 (Observer) 패턴 장단점
장점
상태를 변경하는 객체(publisher)와 변경을 감지하는 객체(subscriber)의 관계를 느슨하게 유지할 수 있다.
Subject의 상태 변경을 주기적으로 조회하지 않고 자동으로 감지할 수 있다.
런타임에 옵저버를 추가하거나 제거할 수 있다.
단점
복잡도가 증가한다.
다수의 Observer 객체를 등록 이후 해지 않는다면 memory leak이 발생할 수도 있다.
memory leak
memory leak 문제의 제일 좋은 솔루션은 unregister를 통해서 해지하는 것이다.
unregister를 하지 않으면 다른 곳에서 참조하지 않더라도 map에 있기 때문에 gc 대상이 되지 않아서
메모리를 계속 점유한다.
약한 참조 (weak reference)를 통해서 해결이 가능할까?
다른 곳에서 참조하지 않으면 자동으로 gc 대상이 되도록 할 수 있지만
이는 gc 대상이 되게 할 뿐이지 해지 시점은 정확하게 알 수 없다.
'Computer Sience > Desgin Pattern' 카테고리의 다른 글
[Design Pattern] 전략 (Strategy) 패턴 (1) | 2024.11.25 |
---|---|
[Design Pattern] 상태(State) 패턴 (1) | 2024.11.25 |
[Design Pattern] 메멘토(Memento) 패턴 (1) | 2024.11.07 |
[Design Pattern] 중재자(Mediator) 패턴 (1) | 2024.11.04 |
[Design Pattern] 이터레이터(Iterator) 패턴 (2) | 2024.10.31 |