CS/디자인패턴

[Design Pattern] Observer pattern(옵저버 패턴)

2024. 1. 24. 00:12
목차
  1. Observer Pattern(옵저버 패턴)이란?
  2. 기존 개발에 대한 문제점( == Observer Pattern이 해결 할 수 있는 문제점)
  3. Observer Pattern 구현 방법
  4. Observer Pattern의 장단점
  5. 자바의 내장 옵저버 객체
  6. java.util.Obserable
  7. java.util.Observer
  8. java.bean.PropertyChangeSuppoter
  9. java.bean.PropertyChangeListener

Observer Pattern(옵저버 패턴)이란?

Observer Pattern은 디자인 패턴(생성, 구조, 행동) 중 행동 디자인 패턴에 속한다.

주체가 어떤 객체(subject)의 상태 변화를 관찰 하다가 상태 변화가 있을 때마다 메서드 등을 통해 옵저버 목록에 있는 옵저버들에게 변화를 알려주는 디자인 패턴이다.

이런 변화를 알려주는 과정에서 흔히 객체가 발행자(publish) 옵저버가 구독자(subscribe)로도 알려져 있으며 이는 객체와 옵저버가 1:N관계를 가지며 분산 이벤트 시스템을 구현하는 데 이용하기도 한다.

 

물론 이렇게만 읽으면 크게 와닿는게 없다..!

 

그래도 저기서 bold처리한 객체와 옵저버가  존재하고 객체(subject)의 상태변화가 있을 때마다 옵저버가 알게되어야 하는 상황이라면 어떤 상황에서든 옵저버 패턴으로 설명이 가능하다.

객체(subject) = > 특정 정보를 발행하는 객체, 관찰 대상자

옵저버(Observer) => 특정 정보를 구독하는 객체, 관찰자

 

인터넷 구글링을 해보면 다른 많은 사람들이 날씨 어플 개발을 통한 예시(그 외에도 유투브 구독, 트위터 서비스 등등)로 설명해주시는게 많은데, 나는 내 생각을 더 정리하고자 내가 만들어낸 예시로 학습해보겠다.

 

예시

많은 예시가 있겠지만 나는 개를 키우는 견주로서 개와 주인들인 우리 가족으로 예시를 두겠다.

개를 객체(subject)로 두고 개의 상태(물을 다 마셨다. 밥을 다 먹었다. 똥을 쌌다 .. )등 상태 변화를 알고 싶은 주인(observer)들이 존재하는 상황을 생각해보자.

 

 

이때 개가 객체(subject)이고 우리가족들(엄마, 아빠, 나)가 옵저버(observer)이다.

 

그럼 대체 이 상황에서 옵저버 패턴을 어떻게 쓰고 언제 쓰는거고 왜 만들어졌을까?

 

자, 물음에 바로 답하기 전에 우선 디자인 패턴이 무엇인지 간단히 짚고 넘어가보자.

소프트웨어 개발에서의 디자인 패턴은 소프트웨어 개발의 일반적인 문제에 대한 해결책으로, 모든 프로그래밍 언어 및 업계의 개발자가 자주 직면하는 문제에 대한 해결책이다. 

 

그럼 우리는 이 정의에서 언제 쓰는 지, 왜 만들어졌는 지에 대해서 두리뭉술하게 말할 수 있다.

어떠한 개발 과정에서 문제가 있었고 이 문제를 해결하기 위한 행동 디자인 패턴인 옵저버 패턴이 만들어졌고, 문제 상황을 옵저버 패턴으로 해결 할 수 있는 경우 그때 사용하면 되는 것이다.

 

그럼 이 Observer Pattern은 어떤 문제에 대한 해결책인지 어떻게 쓰는건지 예시코드와 함께 알아보자.

기존 개발에 대한 문제점( == Observer Pattern이 해결 할 수 있는 문제점)

내가 말했던 개의 상태 변화를 주인이 알고 싶을 때의 예제 코드는 다음과 같다.

public class Dog {
    private String action;

    public void eat() {
        action = "밥을 다 먹었다.";
    }

    public void drink() {
        action = "물을 다 마셨다.";
    }

    public void poop() {
        action = "똥을 쌌다.";
    }

    public String getAction() {
        return action;
    }
}

 

public class Owner {
    private String name;

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

    public void checkDogState(Dog dog) {
        String action = dog.getAction();
        System.out.println(name + "이(가) 강아지의 상태를 확인했습니다. 행동: " + action);
    }
}

 

public class LegacyCodeExample {
    public static void main(String[] args) {
        Dog dog = new Dog();

        Owner owner1 = new Owner("주인1");
        Owner owner2 = new Owner("주인2");

        // 상태 변화
        dog.eat();
        owner1.checkDogState(dog);
        owner2.checkDogState(dog);
        
        dog.drink();
        owner1.checkDogState(dog);
        owner2.checkDogState(dog);
        
        owner1.checkDogState(dog);
        owner2.checkDogState(dog);
        
        dog.poop();
        owner1.checkDogState(dog);
        owner2.checkDogState(dog);
    }
}

 

이 예제 코드에서는 강아지(Dog) 클래스의 상태 변화 메서드가 호출될 때마다 각 주인(Owner)이 직접 checkDogState 메서드를 호출하여 강아지의 상태를 확인할 수 있다. 그러나 이 방식은 주기적으로 상태를 조회하며 상태 변화를 감지하는 방법으로 비효율적이며 실시간으로 상태를 알리기 어렵다. 

 

또한 다른 예시로는 다음과 같다.

public class Owner {
    private String name;
    private Dog dog;  // Owner 클래스가 Dog 객체를 직접 참조

    public Owner(String name, Dog dog) {
        this.name = name;
        this.dog = dog;
    }

    public void checkDogState() {
        String action = dog.getAction();
        System.out.println(name + "이(가) 강아지의 상태를 확인했습니다. 행동: " + action);
    }
}

 

public class LegacyCodeExample {
    public static void main(String[] args) {
        Dog dog = new Dog();

        Owner owner1 = new Owner("주인1", dog);
        Owner owner2 = new Owner("주인2", dog);

        // 상태 변화
        dog.eat();
        owner1.checkDogState();

        dog.drink();
        owner2.checkDogState();

        dog.poop();
        owner1.checkDogState();
    }
}

 

이것은 옵저버 패턴의 장점을 설명하기 이전에 레거시에서의 안좋은 점을 부각하기 위해 추가한 예시이다.

Owner 클래스가  Dog 클래스를 직접적으로 참조하여 매우 강한 결합으로 되어있는 것을 확인 하면 된다.

Observer Pattern 구현 방법

observer pattern의 큰 틀은 다음과 같다.

인터페이스 Subject와 Observer를 설정하는 것이다.

 

Subject Interface

public interface Subject {
    void registerObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObservers(String action);
}

subject의 인터페이스는 observer를 등록, observer 해지, 상태 변화를 observer들에게 알리는 메서드를 가지고 있는 것을 확인 할 수 있다.

 

Observer Interface

public interface Observer {
    void update(String action);
}

observer 인터페이스는 관찰하고 있는 객체의 상태 변화(update)를 옵저버가 알 수 있는 메서드를 가지고 있다.

 

자 이제 이 인터페이스 두개가지고 똑같은 예시인 객체 Dog와 Owner를 구현해보자.


import java.util.ArrayList;
import java.util.List;

public class Dog implements Subject {
    private List<Observer> observers = new ArrayList<>();
    private String action;

    @Override
    public void registerObserver(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }

    @Override
    public void notifyObservers(String action) {
        for (Observer observer : observers) {
            observer.update(action);
        }
    }

    public void eat() {
        action = "밥을 다 먹었다.";
        notifyObservers(action);
    }

    public void drink() {
        action = "물을 다 마셨다.";
        notifyObservers(action);
    }

    public void poop() {
        action = "똥을 쌌다.";
        notifyObservers(action);
    }
}

 

public class Owner implements Observer {
    private String name;

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

    @Override
    public void update(String action) {
        System.out.println(name + "이(가) 강아지의 상태 변화를 감지했습니다. 행동: " + action);
    }
}

 

public class ObserverPatternExample {
    public static void main(String[] args) {
        Dog dog = new Dog();

        Owner owner1 = new Owner("주인1");
        Owner owner2 = new Owner("주인2");

        // 옵저버 등록
        dog.registerObserver(owner1);
        dog.registerObserver(owner2);

        // 상태 변화
        dog.eat();
        dog.drink();
        dog.poop();
    }
}

 

옵저버 패턴을 적용한 이 예제 코드에서 확인 할 수 있다 시피,

이전 레거시 코드(문제가 되는 코드)와 달리 객체의 상태 변화가 있을 때 실시간으로 옵저버들에게 정보가 확인되는 구조이다. 

 

즉, 알고 싶은 객체의 상태 변화를 별도의 함수 호출 없이 즉각적으로 알 수 있기 때문에 이벤트에 대한 처리를 자주해야 하는 프로그램이라면 매우 효율적인 프로그램을 작성할 수 있는 것이다.

Observer Pattern의 장단점

장점

1. Subject의 상태 변화를 주기적으로 조회하지 않아도 된다.

2. Subject와 Observer 객체 사이의 관계가 느슨하게 관리된다.

 == Subject의 코드를 변경하지 않고도 새로운 Observer 클래스를 도입할 수 있어 개방 폐쇄 원칙(OCP) 준수한다

3. Runtime 시점에 Observer를 추가, 제거할 수 있다.

 

단점

1. Observer의 응답 순서를 보장하지 않는다.

2. Observer를 명시적으로 추가, 제거하기 때문에 메모리 누수가 발생할 가능성이 존재한다.

 

 

자바의 내장 옵저버 객체

자바에서 별도로 옵저버 패턴을 직접 구현하지 않아도 옵저버 패턴을 사용할 수 있도록 내장 옵저버 객체들이 존재한다.

1. Obserable과 Observer ( 자바 9버전 부터 deprecated가 되었다 )

2. PropertyChangeSuppoter와 PropertyChangeListener

 

java.util.Obserable

 

자바 9버전 부터는 deprecated가 되었다고 명시되어있고 여기서 확인할 점은 Observable은 class로 구현되어있어 후에 Dog와 같은 클래스를 구현하기 위해서는 extends를 사용하는것, Observer들을 Vector로 관리해 멀티 스레드 및 병렬처리에 안전하다는 점을 확인할 수 있다.

 

이 Observable으로 Dog를 구현해보면 다음과 같다.

import java.util.Observable;

public class Dog extends Observable {

  private String action;
  public void eat() {
    action = "밥을 다 먹었다.";
    this.setChanged();
    this.notifyObservers(action);
  }

  public void drink() {
    action = "물을 다 마셨다.";
    this.setChanged();
    this.notifyObservers(action);
  }

  public void poop() {
    action = "똥을 쌌다.";
    this.setChanged();
    this.notifyObservers(action);
  }
}

 

이 때 또 기존과 다른 점은 this.setChanged()로 변화를 알려줘야 notifyObservers()를 통해 이전과 같이 변화가 있을 때 알려주는것처럼 동작한다는 것이다.

 

java.util.Observer

마찬가지로 자바 9버전부터는 deprecated가 되었다.

 

import java.util.Observable;
import java.util.Observer;

public class Owner implements Observer {
  private String name;

  public Owner(String name) {
    this.name = name;
  }
  
  @Override
  public void update(Observable o, Object arg) {
    if (o instanceof Dog) {
      String action = (String) arg;
      System.out.println(name + "이(가) 강아지의 상태 변화를 감지했습니다. 행동: " + action);
    }
  }
}

 

 


java.bean.PropertyChangeSuppoter

 

Observable처럼 클래스로 구현되어있는 것을 확인할 수있고 작게 thread-safe하다는 것도 확인할 수 있다.

또한 Observer 역할인 PropertyChangeLister들을 Map으로 관리하고 있는 모습이다. 

 

그러나 이전에 상속이나 인터페이스 구현과 같은 방법이 아닌 이 PropertyChangeSupport를 필드로 두어 구현하는것이 다른 차이점 이다. 코드로 확인해보자.

public class Dog {
  PropertyChangeSupport support = new PropertyChangeSupport(this);

  private String action;
  
  public void addObserver(PropertyChangeListener observer) {
    if (observer != null) {
      this.support.addPropertyChangeListener(observer);
    }
  }

  public void removeObserver(PropertyChangeListener observer) {
    if (observer != null) {
      this.support.removePropertyChangeListener(observer);
    }
  }

  public void notifyObservers(String newValue) {
    support.firePropertyChange("event name", null, newValue);
  }

  public void eat() {
    action = "밥을 다 먹었다.";
    this.notifyObservers(action);
  }

  public void drink() {
    action = "물을 다 마셨다.";
    this.notifyObservers(action);
  }

  public void poop() {
    action = "똥을 쌌다.";
    this.notifyObservers(action);
  }
}

 

 

java.bean.PropertyChangeListener

 

 PropertyChangeListener는 인터페이스이고, 차이점은 PropertyChangeEvent인 객체로 달라진 상태(이벤트)를 확인 할 수 있다는 점이다.

 

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

public class Owner implements PropertyChangeListener {

  private String name;

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

  @Override
  public void propertyChange(PropertyChangeEvent evt) {
    System.out.println(name + "이(가) 강아지의 상태 변화를 감지했습니다. 행동: " + evt.getNewValue());
  }
}

 

 

매번 디자인패턴 띡- 생성, 구조, 행동 그리고 무엇 무엇이 있고 정의에 대해서 겉으로만 읽을때와 달리

이렇게 하나의 내가 생각한 예제와 코드를 구현하니 옵저버 패턴에 대해서 조금 더 많이 학습하게 된 것 같다.

 


 

출처 

  • https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%EC%98%B5%EC%A0%80%EB%B2%84Observer-%ED%8C%A8%ED%84%B4-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EB%B0%B0%EC%9B%8C%EB%B3%B4%EC%9E%90#%ED%8C%A8%ED%84%B4_%EC%9E%A5%EC%A0%90
  • https://www.youtube.com/watch?v=DZyyCg-3DVk&t=221
반응형

'CS > 디자인패턴' 카테고리의 다른 글

[Design Pattern] Strategy Pattern(전략 패턴)  (0) 2024.06.02
[Design Pattern] Factory Pattern(팩토리 패턴)  (0) 2024.06.01
[Design Pattern] Singleton Pattern(싱글톤 패턴)  (0) 2024.05.31
[Design Pattern] Builder Pattern(빌더 패턴)  (1) 2024.02.07
[Design Pattern] 디자인 패턴이란?  (0) 2024.01.23
  1. Observer Pattern(옵저버 패턴)이란?
  2. 기존 개발에 대한 문제점( == Observer Pattern이 해결 할 수 있는 문제점)
  3. Observer Pattern 구현 방법
  4. Observer Pattern의 장단점
  5. 자바의 내장 옵저버 객체
  6. java.util.Obserable
  7. java.util.Observer
  8. java.bean.PropertyChangeSuppoter
  9. java.bean.PropertyChangeListener
'CS/디자인패턴' 카테고리의 다른 글
  • [Design Pattern] Factory Pattern(팩토리 패턴)
  • [Design Pattern] Singleton Pattern(싱글톤 패턴)
  • [Design Pattern] Builder Pattern(빌더 패턴)
  • [Design Pattern] 디자인 패턴이란?
three von
three von
어려워 보이는 프로그래밍 언어를 쉽게 정복하는 블로그
반응형
three von
LangEASY : 프로그래밍 언어를 쉽게 정복하는 공간
three von
전체
오늘
어제
  • 분류 전체보기 (89)
    • BackEnd (5)
    • JAVA (5)
      • 기초개념 (5)
    • 자료구조 & 알고리즘 (7)
      • 기초수학 (0)
      • 선형 자료구조 (4)
      • 비선형 자료구조 (1)
      • 알고리즘 (1)
    • CS (18)
      • 컴퓨터구조 (0)
      • 운영체제 (3)
      • 시스템 소프트웨어 (0)
      • 네트워크 (4)
      • 디자인패턴 (10)
    • 데이터베이스 (4)
    • Spring (4)
    • Project (2)
      • 팀프로젝트 (1)
      • 토이프로젝트 (1)
    • 회고 (0)
    • Git&Github (8)
    • IntelliJ (5)
    • 코테 (16)
      • 프로그래머스 (10)
      • 백준 (6)
    • BookStudy (12)
      • 스프링 부트 핵심 가이드 (12)
    • C++ (1)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • spring
  • 윈도우에서 리눅스 명령어
  • 깃 이슈관리
  • 자바 선형자료구조
  • 제로베이스백엔드스쿨
  • 제로베이스백엔드스쿨미니과제
  • 인텔리제이에서 gitbash로 vi vim 에디터 사용하는법
  • github
  • 개발자
  • Java
  • 자바 자료구조 힙
  • github이슈관리
  • 백엔드공부
  • githubTest
  • IntelliJ 자동화
  • windowcmd창
  • 명령어변환
  • LiveTemplate사용
  • 백엔드 스쿨
  • vi/vim
  • 백엔드
  • InteliJ에서 gitbash사용
  • 백엔드스쿨
  • 자바 자바해시맵
  • 코테
  • java heap 자료구조
  • 제로베이스
  • vi/vim에디터사용
  • 리눅스 명령어 윈도우 cmd창에서 가능
  • heap 자료구조

최근 댓글

최근 글

hELLO · Designed By 정상우.
three von
[Design Pattern] Observer pattern(옵저버 패턴)
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.