Java/Design Pattern

[디자인 패턴] 행동 패턴 - 옵저버(관찰자) 패턴 (Observer Pattern)

민돌v 2023. 4. 13. 11:53
(인프런) 코딩으로 학습하는 GoF의 디자인 패턴 - 백기선, 강의를 보고 정리한 글입니다.
코드는 GitHub 에 있습니다

 

#1. 객체 생성 관련 패턴

#2. 구조 관련 패턴

 

#3. 행동 관련 패턴

 

 


✔️ 옵저버 패턴이란 (Observer Pattern)

다수의 객체가 특정 객체 상태 변화를 감지하고 알림을 받는 패턴.
  • 👏🏻 옵저버 디자인 패턴이란, 객체의 상태 변화를 관찰하는 관찰자 객체를 생성하여, 특정한 객체의 상태 변경을 지켜보는 디지인 패턴입니다.
  • 객체의 상태변화를 관찰하는 옵저버(관찰자)들의 목록을 객체에 등록하여 상태 변화가 있을 때 마다 notify를 통해 객체가 직접 목록의 각 옵저버에게 통지하고 → 각각의 옵저버는 이러한 "이벤트"가 발생했을 시 처리할 동작을 수행합니다.
  • 이러한 구조로 객체의 상태가 변화하면, 종속객체들이 자동으로 변화가 통지되어 그에 따른 명령을 수행하도록하는 일대다(One-to-Many)의 의존성을 정의해 줍니다.
  • 즉, 관찰하고 있는 객체의 상태변경에 실시간으로 대응하기 위한 목적을 가지는 패턴입니다.

📌 "상태가 변화되는 객체" 와 이 객체의 상태에 "의존성이 있는 객체" → 이 두 객체가 직접적으로 참조되지 않고 중간에 관찰자 객체를 둠으로써 느슨한 연결구조를 설계합니다.

 

옵저버 패턴이 사용되는 경우

  • pub-sub (발행[publish] - 구독[subscribe]) 이벤트 기반 프로그래밍에 주로 사용되는 패턴입니다.
  • 분산 이벤트 핸들링 시스템에서도 자주 사용이 된다고 합니다.
  • 옵저버 패턴은 MVC 패러다임과 자주 결합되어, Model 과 View 사이를 느슨하게 연결하기 위해 주로 사용된다고 합니다.

 

 

옵저버 패턴

 


✔️ 옵저버 패턴 예시

1) 옵저버 패턴 적용

  • 간단한, 작은 단체 채팅방을 예시로 들어보겠습니다.
  • 구조는 subject 역할을 하는 ChatServer 가 존재하고
  • chatServer 의 register 즉, 단체톡방을 Observer 로
  • 단체톡방에 종속된 객체인 ConcreateObserver 를 User 클래스롤 구현하겠습니다.

 

그럼 ChatServer 이벤트 발생 → 등록된 Observer가 관찰 → ConcreteObserver 들의 행위 실행이 됩니다.

 

👇

 

Observer 

  • 먼저 옵저버 클래스입니다.
  • Subscriber 라는 인터페이스로 정의하고, notify 메소드를 저으이합니다.
//Observer
public interface Subscriber {

    void notifyHandleMessage(String message);

}

 

ConcreteObserver

  • 그 다음은 Observer 를 상속받아 각각의 행위를 캡슐화하여 구현한 ConcreteObserver 클래스 입니다.
  • 저는 User라는 이름의 개체를 만들어주었고
  • 이벤트가 발생했을 때 실행할 notify를 정의해 주었습니다.
//ConcreteObserver
@Getter
@AllArgsConstructor
public class User implements Subscriber{

    private String name;

    @Override
    public void notifyHandleMessage(String message) {
        System.out.println("받는사람 (" + name + ") " + message );
    }
}

 

Subject 

  • subject 클래스로 채팅서버를 두었습니다.
  • Subject 클래스에서 이벤트 등록, 해지, 발생의 책임을 담당합니다.
public class ChatServer {

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

    //등록 - Observer 등록
    public void register(String group, Subscriber subscriber) {
        if (subscribers.containsKey(group)) {
            subscribers.get(group).add(subscriber);

            return;
        }

        List<Subscriber> list = new ArrayList<>();
        list.add(subscriber);
        this.subscribers.put(group, list);
    }

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

    //이벤트 발생
    public void sendMessage(User user, String group, String message) {
        String userMessage = "보내는사람 ("+user.getName() + ") : " + message;

        // observer list 에 등록된 각각의 옵저버들의 notify 실행
        if (subscribers.containsKey(group)) {
            System.out.println("========= 단톡방 : " + group + " =======");
            this.subscribers.get(group).forEach(s -> s.notifyHandleMessage(userMessage));
        }
    }
}

 

Client (실행)

  • 각각의 유저를 정의하고, 해당 유저를 ChatServer 의 Observer(채팅방) 에 등록합니다.
  • ChatServer (Subject) 의 특정한 이벤트 (sendMessage) 가 발행될 떄, 이를 바라보고 있던 Observer 클래스들의 notify 가 실행됩니다.
public class Client {

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

        chatServer.register("오징어게임", user1);
        chatServer.register("오징어게임", user2);

        chatServer.register("디자인 패턴", user1);

        chatServer.sendMessage(user1, "오징어게임", "아 이름이 기억났어 ~~ 오일남이야");

        System.out.println();

        chatServer.sendMessage(user2, "디자인 패턴", "오저버 패턴으로 만든 패턴");

    }
}

 

 

 

 
 

✔️ 옵저버 패턴 장단점

장점

  • 상태를 변경하는 객체와 변경을 감지하는 객체의 의존성을 느슨하게 유지할수있습니다.
    • → 강결합 되어있는 두 서비스를 분리하여 의존성과 결합도를 낮춘다.
  • subject의 상태변경을 주기적으로 조회하지 않아도 자동적으로 감지할 수 있습니다.
  • 런타임시에 옵저버를 추가하거나 제거할 수 있습니다.

 

 

단점

  • 복잡도가 증가합니다.
  • 패턴을 잘못 구현할 경우 데이터 배분에 문제가 발생하여 위험도가 큽니다.
  • 다수의 Observer 객체를 등록 이후 해지 않는다면 memory leak이 발생할 수도 있다.
    • 위의 예시에서, Map 에 담겨있는 Observer 가 명시적을 해제되지 않고, Map 안에서 Reference(참조)를 가지고 있다면 가비지 컬렉션의 대상 되지 않음
    • 즉, 이벤트가 계속쌓이고, 메모리 관리가 되지 않는다면 시한폭탄이나 다름없다