반응형

 

의도

  • 다수의 객체가 특정 객체 상태 변화를 감지하고 알림을 받는 패턴
  • 객체 사이에 일 대 다의 의존 관계를 정의해 두어, 어떤 객체의 상태가 변할 때 그 객체에 의존성을 가진 다른 객체들이 그 변화를 통지받고 자동으로 갱신될 수 있게 만든다.

 

장점

  • 상태를 변경하는 객체(publisher)와 변경을 감지하는 객체(subsriber)의 관계를 느슨하게 유지할 수 있다.
  • Subject의 상태 변경을 주기적으로 조회하지 않고 자동으로 감지할 수 있다.
  • 런타임에 옵저버를 추가하거나 제거할 수 있다.

 

단점

  • 복잡도가 증가한다.
  • 다수의 Observer 객체를 등록 이후 해지하지 않는다면 메모리 누수가 발생할 수도 있다.

 

알려진 사용 예

  • 자바
    • Observer 인터페이스
    • PropertyChangeListener
    • PropertyChangeSupport
    • Flow API

 

활용성

  • 어떤 추상 개념이 두 가지 양상을 갖고 하나가 다른 하나에 종속적일 때
    • 각 양상을 별도의 객체로 캡슐화하여 이들 각각을 재사용할 수 있다.
  • 한 객체에 가해진 변경으로 다른 객체를 변경해야 하고 어떤 객체들이 변경되어야 하는지 몰라도 될 때
  • 어떤 객체가 다른 객체에 자신의 변화를 통보할 수 있는데, 그 변화에 관심있어 하는 객체들이 누구인지에 대한 가정 없이도 그러한 통보가 될 때

 

결과

  • Subject와 Observer 클래스 간에는 추상적인 결합도만 존재한다.
    • 주체가 아는 것은 감시자들의 리스트 뿐이다.
    • 감시자들은 Observer클래스에 정의된 인터페이스를 따른다.
    • 주체는 어떤 ConcreteObserver 클래스가 있는지 알 필요가 없다.
  • 브로드캐스트(Broadcast) 방식의 교류를 가능하게 한다.
    • 감시자 패턴에서 주체가 보내는 통보는 구체적인 수신자를 지정할 필요가 없다.
    • 이 통보는 주체의 정보를 원하는 모든 객체에 자동으로 전달되어야 한다.
    • 주체는 단지 자신의 감시자에게만 상태 변화 사실을 알려주면 감시자가 이 통보를 처리할지 무시할지를 결정한다.
  • 예측하지 못한 정보를 갱신한다.
    • 감시자는 다른 감시자의 존재를 모르기 때문에 주체를 변경하는 비용이 어느 정도인지 알 수 없다.
    • 주체에 계속적으로 어떤 연산이 가해질 때 감시자와 주체에 종속된 다른 객체들의 연속적인 수정이 발생될 수 있다.
    • 종속성 기준 때문에 불필요한 갱신이 일어날 수도 있고 추적도 까다로울 수 있다.
    • 감시자가 무엇이 변했는지 알 수 있게 해 주는 별도의 프로토콜이 없다면 변경을 추적하기는 어렵다.

 

협력 방법

  • ConcreteSubject는 Observer의 상태와 자신의 상태가 달라지는 변경이 발생할 때마다 감시자에게 통보한다.
  • ConcreteSubject에서 변경이 통보된 후, ConcreteObserver는 필요한 정보를 주체에게 질의하여 얻어온다.
  • ConcreteObserver는 이 정보를 이용해서 주체의 상태와 자신의 상태를 일치시킨다.

 

구조

실제 구현 구조

소스코드

//Observer
public interface Subscriber {

    void handleMessage(String message);
}
//ConcreteObserver
public class User implements Subscriber {

    private String name;

    public User(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    @Override
    public void handleMessage(String message) {
        System.out.println(message);
    }
}
//ConcreteSubject
public class ChatServer {

    private Map<String, List<Subscriber>> subscribers = new HashMap<>();

    //Attach
    public void register(String subject, Subscriber subscriber) {
        if (this.subscribers.containsKey(subject)) {
            this.subscribers.get(subject).add(subscriber);
        } else {
            List<Subscriber> list = new ArrayList<>();
            list.add(subscriber);
            this.subscribers.put(subject, list);
        }
    }

    //Detach
    public void unregister(String subject, Subscriber subscriber) {
        if (this.subscribers.containsKey(subject)) {
            this.subscribers.get(subject).remove(subscriber);
        }
    }

    public void sendMessage(User user, String subject, String message) {
        if (this.subscribers.containsKey(subject)) {
            String userMessage = user.getName() + ": " + message;
            this.subscribers.get(subject).forEach(s -> s.handleMessage(userMessage));
        }
    }
}
public class Client {

    public static void main(String[] args) {
        ChatServer chatServer = new ChatServer();
        User user1 = new User("userA");
        User user2 = new User("userB");

        //구독
        chatServer.register("블랙리스트", user1);
        chatServer.register("블랙리스트", user2);

        //구독
        chatServer.register("디자인패턴", user1);
        chatServer.register("디자인패턴", user2);

        chatServer.sendMessage(user1, "블랙리스트", "스튜메이커");
        chatServer.sendMessage(user2, "디자인패턴", "옵저버 패턴으로 만든 채팅");

        chatServer.unregister("디자인패턴", user2);

        chatServer.sendMessage(user2, "디자인패턴", "옵저버 패턴 장, 단점 보는 중");
    }
}

 


[참고자료]

리처드 헬름, 랄프 존슨, 존 블리시디스, 『GoF의 디자인 패턴 : 재사용성을 지닌 객체지향 소프트웨어의 핵심요소』, 김정아 번역, 프로텍미디어(2015)

http://www.cs.unc.edu/~stotts/GOF/hires/pat5gfso.htm

코딩으로 학습하는 GoF의 디자인 패턴, 백기선

 

반응형

+ Recent posts