옵저버(Observer) 패턴
디자인 패턴 ·객체의 상태변화가 있을때 변경사항을 알려줄 수 있는 디자인 패턴입니다.
구성 요소
- 서브젝트(Subject): 이벤트를 발생시키는 객체입니다
- 옵저버(Observer): Subject에 변화가 생겼을 때 알림을 받는 대상입니다.
구현:
- 주체(Subject) 인터페이스 설계: 주체는 옵저버들을 등록하고 알림을 보내는 메서드를 제공해야 합니다. 주체 인터페이스를 설계하여 주체 객체가 구현해야 할 메서드를 정의합니다. 예를 들어, 다음과 같은 메서드를 포함할 수 있습니다:
- attach(observer): 옵저버를 주체에 등록합니다.
- detach(observer): 주체에서 옵저버를 제거합니다.
- notify(): 주체의 상태 변경을 알림으로써 등록된 모든 옵저버에게 전달합니다.
- 옵저버(Observer) 인터페이스 설계: 옵저버는 주체로부터 알림을 받고 상태를 업데이트하는 메서드를 제공해야 합니다. 옵저버 인터페이스를 설계하여 옵저버 객체가 구현해야 할 메서드를 정의합니다. 예를 들어, 다음과 같은 메서드를 포함할 수 있습니다:
- update(): 주체로부터의 알림을 받고 상태를 업데이트하는 메서드입니다. 이 메서드는 주체가 전달하는 데이터나 이벤트를 처리하는 로직을 포함해야 합니다.
- 주체(Subject) 클래스 구현: 주체 인터페이스를 구체적으로 구현하는 클래스를 작성합니다. 주체 클래스는 등록된 옵저버들을 추적하고, 상태 변경 시에 등록된 옵저버들에게 알림을 보내는 기능을 구현해야 합니다.
- 옵저버(Observer) 클래스 구현: 옵저버 인터페이스를 구체적으로 구현하는 클래스를 작성합니다. 각각의 옵저버 클래스는 주체로부터의 알림을 수신하여 필요한 작업을 수행합니다.
- 옵저버 등록과 알림: 주체 객체를 생성한 후에는 옵저버 객체를 생성하고 주체에 등록해야 합니다. 이후 주체의 상태 변경이 발생하면 주체는 등록된 옵저버들에게 알림을 보내야 합니다. 이를 위해 주체는 등록된 옵저버들의 update() 메서드를 호출하며, 필요한 데이터나 이벤트를 인자로 전달할 수 있습니다.
장점
- 느슨한 결합 (Loose Coupling): 주체와 옵저버 간의 상호작용이 인터페이스를 통해 이루어지므로, 주체와 옵저버는 서로 독립적으로 변경될 수 있습니다. 이로 인해 시스템의 유연성과 재사용성이 증가합니다.
- 확장성 (Scalability): 새로운 옵저버를 추가하거나 기존의 옵저버를 제거하기 쉽습니다. 주체에 대한 의존성을 가진 다양한 옵저버를 추가할 수 있으며, 이를 통해 기능을 확장할 수 있습니다.
- 이벤트 기반 시스템 구현: 옵저버 패턴은 이벤트 기반 시스템을 구현하는 데 유용합니다. 주체의 상태 변경을 이벤트로 간주하고, 해당 이벤트에 대응하는 옵저버들이 처리하도록 할 수 있습니다.
- 단일 책임 원칙 (Single Responsibility Principle): 주체와 옵저버의 분리로 인해 각각의 객체는 자신의 역할에 집중할 수 있습니다. 주체는 상태 변경을 관리하고, 옵저버는 상태 변경에 대한 반응을 처리합니다.
단점
- 성능 영향: 옵저버 패턴은 많은 수의 옵저버가 등록되어 있는 경우에는 알림의 처리가 오버헤드를 초래할 수 있습니다. 또한, 옵저버들 간에 순서나 우선순위가 필요한 경우에도 추가적인 관리가 필요할 수 있습니다.
- 알림 순서의 불확실성: 옵저버들에게 알림을 보내는 순서는 보장되지 않습니다. 따라서, 옵저버들 사이에 의존성이 있을 경우 순서에 따라 결과가 달라질 수 있습니다.
- 디버깅과 추적의 어려움: 옵저버 패턴을 사용하면 데이터나 이벤트의 흐름을 추적하기 어려울 수 있습니다. 옵저버들의 동작과 상호작용을 이해하려면 디버깅이나 로깅을 통해 추적해야 할 수 있습니다.
예시
챗서버를 만드려고한다. 사용자는 챗서버에서 원하는 주제를 선택하면 해당 주제의 채팅이 올라올때마다 알림이 온다.
Observer
public interface ChatServerSubscriber {
void handleMessage(String message);
}
Concreate Observer
public class User implements ChatServerSubscriber{
private String name;
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public void handleMessage(String message) {
System.out.println("(" + this.hashCode() +") " + message);
}
}
Subject
public class ChatServer {
private final Map<String, List<ChatServerSubscriber>> subscribers;
public ChatServer() {
this.subscribers = new HashMap<>();
}
public void register(String subject, ChatServerSubscriber...subscriber) {
List<ChatServerSubscriber> asList = Arrays.asList(subscriber);
if (this.subscribers.containsKey(subject)) {
this.subscribers.get(subject).addAll(asList);
} else {
ArrayList<ChatServerSubscriber> newList = new ArrayList<>(asList);
this.subscribers.put(subject, newList);
}
Arrays.stream(subscriber).forEach(s->s.handleMessage(subject + " " + "registered"));
}
public void unRegister(String subject, ChatServerSubscriber...subscriber) {
this.subscribers.get(subject).removeAll(Arrays.asList(subscriber));
Arrays.stream(subscriber).forEach(s->s.handleMessage(subject + " " + "unregistered"));
}
public void sendMessage(String name, String subject, String message){
this.subscribers.get(subject).forEach(s->s.handleMessage("[" + subject + "] " + name + " : " + message));
}
}
Client
class ChatServerTest {
@Test
public void chatServer_test() {
ChatServer chatServer = new ChatServer();
User kktrkkt = new User("kktrkkt");
User shlee = new User("shlee");
chatServer.register("디자인패턴", kktrkkt);
chatServer.register("롤드컵2021", kktrkkt);
chatServer.register("디자인패턴", shlee);
chatServer.sendMessage(kktrkkt.getName(), "디자인패턴", "이번엔 옵저버 패턴입니다.");
chatServer.sendMessage(kktrkkt.getName(), "롤드컵2021", "LCK 화이팅!");
chatServer.unRegister("디자인패턴", kktrkkt);
chatServer.sendMessage(shlee.getName(), "디자인패턴", "예제 코드 보는 중..");
}
}
결론
옵저버 패턴은 상태 변경을 감지하고 이에 대한 작업을 수행하는 객체 간의 효율적인 통신을 위해 사용됩니다. 유연성과 확장성을 제공하며, 이벤트 기반 시스템과 MVC 아키텍처 등 다양한 상황에서 유용하게 적용될 수 있습니다. 하지만 성능과 순서에 관한 고려사항을 고려해야 하며, 디버깅과 추적의 어려움에 대비하여 적절한 로깅과 디버깅 도구를 활용해야 합니다.
예제는 이곳 에서 확인하실수 있습니다.