옵저버(Observer) 패턴

객체의 상태변화가 있을때 변경사항을 알려줄 수 있는 디자인 패턴입니다.

구성 요소

구현:

  1. 주체(Subject) 인터페이스 설계: 주체는 옵저버들을 등록하고 알림을 보내는 메서드를 제공해야 합니다. 주체 인터페이스를 설계하여 주체 객체가 구현해야 할 메서드를 정의합니다. 예를 들어, 다음과 같은 메서드를 포함할 수 있습니다:
    • attach(observer): 옵저버를 주체에 등록합니다.
    • detach(observer): 주체에서 옵저버를 제거합니다.
    • notify(): 주체의 상태 변경을 알림으로써 등록된 모든 옵저버에게 전달합니다.
  2. 옵저버(Observer) 인터페이스 설계: 옵저버는 주체로부터 알림을 받고 상태를 업데이트하는 메서드를 제공해야 합니다. 옵저버 인터페이스를 설계하여 옵저버 객체가 구현해야 할 메서드를 정의합니다. 예를 들어, 다음과 같은 메서드를 포함할 수 있습니다:
    • update(): 주체로부터의 알림을 받고 상태를 업데이트하는 메서드입니다. 이 메서드는 주체가 전달하는 데이터나 이벤트를 처리하는 로직을 포함해야 합니다.
  3. 주체(Subject) 클래스 구현: 주체 인터페이스를 구체적으로 구현하는 클래스를 작성합니다. 주체 클래스는 등록된 옵저버들을 추적하고, 상태 변경 시에 등록된 옵저버들에게 알림을 보내는 기능을 구현해야 합니다.
  4. 옵저버(Observer) 클래스 구현: 옵저버 인터페이스를 구체적으로 구현하는 클래스를 작성합니다. 각각의 옵저버 클래스는 주체로부터의 알림을 수신하여 필요한 작업을 수행합니다.
  5. 옵저버 등록과 알림: 주체 객체를 생성한 후에는 옵저버 객체를 생성하고 주체에 등록해야 합니다. 이후 주체의 상태 변경이 발생하면 주체는 등록된 옵저버들에게 알림을 보내야 합니다. 이를 위해 주체는 등록된 옵저버들의 update() 메서드를 호출하며, 필요한 데이터나 이벤트를 인자로 전달할 수 있습니다.

장점

단점

예시

챗서버를 만드려고한다. 사용자는 챗서버에서 원하는 주제를 선택하면 해당 주제의 채팅이 올라올때마다 알림이 온다.

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 아키텍처 등 다양한 상황에서 유용하게 적용될 수 있습니다. 하지만 성능과 순서에 관한 고려사항을 고려해야 하며, 디버깅과 추적의 어려움에 대비하여 적절한 로깅과 디버깅 도구를 활용해야 합니다.

예제는 이곳 에서 확인하실수 있습니다.