Java 디자인 패턴-09.Observer

(Java 디자인 패턴 스터디 모집 중 : https://github.com/bluedskim/javaDesignPatterns)

Observer 패턴 다이어그램

Design Pattern: The Observer Pattern (출처:https://dev.to/danlee0528/design-pattern-the-observer-pattern-3oha)

해결하려는 문제 : 알림의 주체는 누구인가?

  • push vs poll

    어떤 이벤트가 발생하면 진행되어야 할 후속 이벤트들이 있다면 이벤트를 발생하는쪽이 알려줘야(Push) 하는가? 아니면 이벤트를 수신하는 쪽이 이벤트를 관찰(Poll)하고 있어야 하는가?

  • 알려줘야 할 대상(Subscriber)들을 소스코드에 하드코딩하여 관리해야 하는가

특징/용도

  1. publisher-subscriber(pub-sub)이라고도 한다.
  2. 객체들은 one(publisher)-to-many(subscriber) 관계를 가진다.
  3. subscriber는 publisher알지만 publisher는 subscriber를 알지 못한다. 즉 관계는 단방향이다.

고려사항

  1. event driven아키텍처의 분산 이벤트 관리에서 많이 사용된다(https://en.wikipedia.org/wiki/Observer_pattern)
  2. 동시성 처리를 위해 observer가 subject-events를 수신할 때 멀티쓰레드로 동작할 필요도 있다.

클래스 다이어그램

Observer 패턴(김동석)

소스

  • PropertyChangeSupport 를 사용한 observer 패턴 샘플
  • observable(Subject, Publisher) : NewsAgency.java
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    
    package net.dskim.desingpattern.observer;
    
    import java.beans.PropertyChangeListener;
    import java.beans.PropertyChangeSupport;
    
    /**
     * Subject(Publisher)
     */
    public class NewsAgency {
    	private String previousNews;
    
    	private PropertyChangeSupport support;
    
    	public NewsAgency() {
    		support = new PropertyChangeSupport(this);
    	}
    
    	public void addPropertyChangeListener(PropertyChangeListener pcl) {
    		support.addPropertyChangeListener(pcl);
    	}
    
    	public void removePropertyChangeListener(PropertyChangeListener pcl) {
    		support.removePropertyChangeListener(pcl);
    	}
    
    	public void setNews(String news) {
    		support.firePropertyChange("news", this.previousNews, news);
    		this.previousNews = news;
    
    	}
    }
  • observer(Listener, Subscriber) : NewsChannel.java
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    package net.dskim.desingpattern.observer;
    
    import java.beans.PropertyChangeEvent;
    import java.beans.PropertyChangeListener;
    
    /**
     * Listener(Subscriber)
     */
    public class NewsChannel implements PropertyChangeListener {
    
    	private String news;
    
    	@Override
    	public void propertyChange(PropertyChangeEvent evt) {
    		news = (String) evt.getNewValue();
    	}
    
    	public String getNews() {
    		return news;
    	}
    }
  • client : ObserverTest.java
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    
    package net.dskim.desingpattern.observer;
    
    import static org.junit.jupiter.api.Assertions.assertEquals;
    
    import org.junit.jupiter.api.Test;
    
    import lombok.extern.slf4j.Slf4j;
    
    @Slf4j
    public class ObserverTest {
    
    	@Test
    	public void whenChangingPCLNewsAgencyState_thenONewsChannelNotified() {
    		String newsName = "뉴스 제목";
    
    		NewsAgency observable = new NewsAgency();
    		NewsChannel observer1 = new NewsChannel();
    		NewsChannel observer2 = new NewsChannel();
    
    		observable.addPropertyChangeListener(observer1);
    		observable.addPropertyChangeListener(observer2);
    		observable.setNews(newsName);
    
    		log.info("observer.getNews()={}", observer1.getNews());
    		assertEquals(observer1.getNews(), newsName);
    		assertEquals(observer2.getNews(), newsName);
    	}
    }

참고