일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- Codeforces Round #802 (Div. 2)
- 단계별로 풀어보기
- Python
- GROUP BY 절
- Java11
- 파이썬
- 응용
- 공공데이터
- 개념
- SQLD / SQLP
- 명품 자바 프로그래밍
- HAVING 절
- 백준
- JAVA 11
- Python 3
- Codeup
- 기초
- 자바
- BOJ
- 이론
- programmers
- level1
- SELECT 절
- 기초100제
- baekjoon
- java
- 헤드퍼스트 디자인패턴
- pypy3
- 기본
- 코딩테스트
- Today
- Total
Development Project
[ 헤드퍼스트 디자인패턴 - 11/08 ] Chap2. 옵저버 패턴 본문
헤드퍼스트 디자인패턴 개정판을 읽은 후 터득한 내용을 정리해 올린 포스팅임을 밝힙니다.
▶ 기상 모니터링 애플리케이션 알아보기
업무 계약 체결서에 따르면,
WeatherData 객체는 현재 기상 조건(온도, 습도, 기압)을 추적하여 가지고있는데,
이 객체를 바탕으로 현재 조건, 기상통계, 간단한 기상예보를 표시하는 애플리케이션을 제작요청함.
위 객체의 값들은 최신 측정치를 수집할때마다 실시간으로 갱신되어야 한다고 덧붙임.
기상 모니터링 애플리케이션 분석
해당 회사에서 제공한 부분 + 만들어야할 부분 + 후에 확장해야할 부분 들을 파악해야함
해당 시스템은 실제 기상정보를 수집하는 물리장비 / WeatherData객체 / 사용자에게 보여줄 장치로 구성
WeatherData객체는?
구조 | 코드 |
WeatherData getTemperature() getHumidity() getPressure() measurementsChanged() // 기타 메소드 |
// 게터들은 기기에서 가져오는 부분이므로 생략 public void measurementsChanged(){ // 코드가 들어갈 자리 } |
단순하게 구현 해보기
public class WeatherData{
// 인스턴스 변수 선언
public void measurementsChanged(){
// 온도, 습도, 기압을 게터로 받아옴
float temp = getTemperature();
float humidity = getHumidity();
float pressure = getPressure();
// 각각 현재 조건, 기상통계, 간단한 기상예보를 보여주는 화면에 보내기
currentComditionsDisplay.update(temp, humidity, pressure);
statisticsDisplay.update(temp, humidity, pressure);
forecastDisplay.update(temp, humidity, pressure);
간단하게 전부 구현된 것 처럼 보이지만 사실상 큰 문제들이 있다.
① 인터페이스가 아닌 구체적 표현을 바탕으로 코딩하고 있다.
=> 해당 디스플레이 이름처럼 구체적 표현을 사용했기 때문에 프로그램을 고치지 않고서는 다른 디스플레이 항목을 추가하거나 삭제가 불가하다. 즉, 유연하지 않은 강한 결합이기 때문에 변화에 용이하지 않으므로 지양해야한다.
② 새로운 디스플레이 항목이 추가될 때마다 코드를 변경해야한다.
=> 해당 디스플레이로 정보를 제각각 보내고 있으므로, 더 추가하거나 삭제하는 등의 처리가 필수적이다.
③ 실행 중에 디스플레이 항목을 추가하거나 제거할 수 없다.
=> 세터가 없기때문에 당연하게도 불가
④ 바뀌는 부분을 캡슐화하지 않았다.
=> 온도, 습도, 기압을 float형태로 받고 그대로 여러 디스플레이로 넘겨주고있다.
해당 단점들을 극복하기 위한 패턴이 바로 옵저버 패턴이다!
▶ 옵저버 패턴 이해하기
옵저버 패턴을 이해하기 위해서는 크게 "구독 형태"를 생각하면 된다.
책에서는 신문을 예로 들었지만, 좀더 이해하기 쉽도록 Youtube로 설명하고자 한다.
Youtube의 구독형태는?
① 유튜브에 가입한 유튜버들이 영상을 찍고 열심히 편집해 업로드한다.
② 유저가 원하는 유튜버 채널에 구독을 누르면, 새로운 영상이 업로드 될 경우 알림이 오고 구독페이지에서 바로 접근이 가능하다.
③ 해당 유튜버의 영상을 더이상 보길 원치않는다면, 구독을 해지한다.
④ 유튜버가 계정을 삭제하지 않는 한, 사용자들은 꾸준히 구독하거나 해지하는 작업을 수행한다.
옵저버 패턴의 형태를 Youtube에 비춰보면?
※ Youtube == 주제(Subject) / 구독자 == 옵저버(observer)
1 | Youtube에서 영상들을 관리합니다. |
Subject(주제)에서 중요한 데이터를 관리합니다 | |
2 | 영상이 바뀌면 구독자들에게 알림이 갑니다. |
Subject 데이터가 바뀌면 옵저버에게 그 소식이 전해집니다. | |
3 | 각 구독자들은 Youtube를 구독하고 있으며(== Youtube 사용자로 등록) Youtube의 데이터가 바뀌면 갱신 내용을 알림이나 구독페이지로 볼 수 있습니다. |
옵저버 객체들은 주제를 구독하고 있으며(== 주제 객체에 등록) 주제 데이터가 바뀌면 갱신 내용을 전달받습니다. | |
4 | 구독자가 아닌 사용자는 따로 연락이 오지 않습니다. |
옵저버에 속하지 않은 객체는 Subject 데이터가 바뀌어도 아무런 연락도 받지 못합니다. |
옵저버 패턴의 작동원리 정리
① 옵저버에 소속되지 않은 객체가 주제에게 옵저버 요청
② 주제가 해당 객체를 구독목록에 갱신하고 옵저버로 등록
③ 주제의 값이 갱신되면 모든 옵저버가 연락을 받음
④ 옵저버에 소속된 객체가 주제에게 탈퇴요청
⑤ 주제가 요청을 받아들여 옵저버 집합에서 제외
⑥ 나간 객체는 더이상 옵저버가 아니므로 연락을 받지 못함
옵저버 패턴(Observer Pattern)의 정의
한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체에게 연락이 가고 자동으로 내용이 갱신되는 방식
일대다( one[주제] - to - many[옵저버] ) 의존성을 정의함
옵저버 패턴의 구조
Subject [인터페이스] registerObserver() removeObserver() notifyObservers() |
옵저버 ---------> |
Observer [인터페이스] update() |
↑ ㅣ ㅣ |
↑ ㅣ ㅣ |
|
ConcreteSubject registerObserver() { ... } // 옵저버 등록 removeObserver() { ... } // 옵저버 제거 notifyObservers() { ... } // 모든 옵저버에게 연락 getState() setState() |
주제 <--------- |
ConcreteObserver update() |
옵저버 패턴에서는 주제가 상태를 저장하고 제어하기 때문에, 상태가 들어있는 객체는 하나만 있을 수 있음.
반면에, 옵저버는 상태를 사용하지만 소유할 필요가 없음 ( 구독자가 영상을 볼 뿐이지, 가져야하는 것은 아님 )
즉, 옵저버는 주제에서 상태가 바뀌었다는 사실을 알려주길 기다리는 의존적 성질을 가지게 되므로 일대다관계 성립한다!
▶ 느슨한 결합을 위한 옵저버 패턴
느슨한 결합(Loose Coupling) : 객체들이 상호작용할 수는 있지만, 서로를 잘 모르는 관계
=> 유연성을 얻기위해 필수적임!
어떻게 옵저버 패턴이 느슨한 결합을 만들까
① 주제는 옵저버가 특정 인터페이스를 구현한다는 사실만 안다.
=> 옵저버의 구상클래스가 무엇인지, 옵저버가 무엇을 하는지 알 필요도 없음
Youtube에서 해당 구독자가 어디서 뭘 하는 사람인지 알 이유가 없다는 것과 같은 말
② 옵저버는 언제든지 새로 추가 가능하다.
=> 주제는 Observer 인터페이스를 구현하는 객체의 목록에만 의존하므로 언제든지 새로운 옵저버를 추가할 수 있음
③ 새로운 형식의 옵저버를 추가할 때도 주제를 변경할 이유가 없다.
=> 옵저버 인터페이스만 구현한다면 어떤 객체에게도 연락이 가능하기 때문!
④ 주제와 옵저버는 서로 독립적으로 재사용할 수 있다.
=> 서로 단단히 결합되어있지 않기때문에, 다른 용도로 활용할 일이 있다해도 손쉽게 재사용 가능
⑤ 주제나 옵저버가 달라져도 서로에게 영향을 미치지는 않는다.
=> 독립적이기 때문에!
단단하게 짠 바구니가 유연하게 짠 바구니보다 부서지기 쉽다!
↓
Design Principle. 상호작용하는 객체 사이에는 가능하면 느슨한 결합을 쓰자
< 변경사항이 생겨도 무난히 처리할 수 있는 유연한 객체지향 시스템을 구축할 수 있다! >
▶ 기상 스테이션 설계 및 구현하기
인터페이스 구현
// Subject.java
public interface Subject {
// 옵저버를 등록
public void registerObserver(Observer o);
// 옵저버를 제거
public void removeObserver(Observer o);
// 모든 옵저버에게 연락하는 메소드
public void notifyObservers();
}
// Observer.java
public interface Observer {
// 옵저버에게 측정한 값들 전달
public void update(float temp, float humidity, foat pressure);
}
// DisplayElement.java
public interface DisplayElement {
// 화면에 출력하기위한 메소드
public void display();
}
인터페이스에 따른 구상클래스 구현 - Subject
// SimpleSubject.java
public class SimpleSubject implements Subject {
// 옵저버 객체들을 저장하는 리스트
private List<Observer> observers;
private int value;
// 생성자 - 객체생성
public SimpleSubject() {
observers = new ArrayList<Observer>();
}
// 옵저버가 등록을 요청하면, 목록에 추가함
public void registerObserver(Observer o) {
observers.add(o);
}
// 옵저버가 탈퇴를 요청하면, 목록에서 제거
public void removeObserver(Observer o) {
observers.remove(o);
}
// 모든 옵저버에게 상태변화를 알려줌
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(value);
}
}
// 변경사항을 설정할때 notifyObservers 메소드를 호출!
public void setValue(int value) {
this.value = value;
notifyObservers();
}
}
인터페이스에 따른 구상클래스 구현 - Observer
// SimpleObserver.java
public class SimpleObserver implements Observer {
private int value;
private Observable observable;
// 생성자 - 객체생성
public SimpleObserver(Observable observable) {
this.observable = observable;
observable.addObserver((Observer) this);
}
// 출력용
public void display() {
System.out.println("Value: " + value);
}
@Override
public void update(Observable o, Object arg) {
System.out.println(arg);
this.value = (int) arg;
display();
// 주제에게 받은 내용을 넣음
if (o instanceof SimpleSubject) {
SimpleSubject simpleSubject = (SimpleSubject)o;
this.value = simpleSubject.getValue();
display();
}
}
}
디스플레이 구현
public class CurrentConditionsDisplay implements Observer, DisplayElement {
private int value;
private SimpleSubject simpleSubject;
public CurrentConditionsDisplay(SimpleSubject simpleSubject) {
this.simpleSubject = simpleSubject;
simpleSubject.registerObserver(this);
}
public void update(int value){
this.value = value;
display();
}
public void display() {
System.out.println("현재 값 : "+value);
}
}
실제로는 온습도, 기압을 나타내야하지만, 위에서 구현한 바는 전부 value였으므로 그대로 value로 나타내었다.
▶ 라이브러리 속 옵저버 패턴 알아보기
옵저버 패턴을 사용한 라이브러리 중 스윙(Swing) 라이브러리에 대해 알아보자.
Swing 라이브러리
스윙은 인터페이스 용도의 GUI 툴킷인데, 기본 구성 요소 중 하나인 JButton 클래스를 분석해보면,
JButton의 슈퍼클래스인 AbstractButton을 보면 리스너를 추가하고 삭제하는 메소드들이 가득하다.
간단한 Swing라이브러리 테스트
"할까 말까?" 가 작성된 버튼을 누르면 리스너가 "그냥 저질러 버렷!!!" 혹은 "하지 마! 아마 후회할 걸?"이라는 텍스트를 출력하도록 해보자.
import java.awt.*;
import javax.swing.*;
public class SwingObserverExample {
JFrame frame;
// 프레임을 만들고 그 안에 버튼을 추가
public static void main(String[] args) {
SwingObserverExample example = new SwingObserverExample();
example.go();
}
public void go() {
frame = new JFrame();
JButton button = new JButton("할까 말까?");
// 람다 없이 구현하려면 객체를 꼭 생성해야함.
//button.addActionListener(new AngelListener());
//button.addActionListener(new DevilListener());
// 람다를 사용할 경우
button.addActionListener(event ->
System.out.println("하지 마! 아마 후회할 걸?")
);
button.addActionListener(event ->
System.out.println("그냥 저질러 버렷!!!")
);
// 프레임 속성을 설정
frame.getContentPane().add(BorderLayout.CENTER, button);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(BorderLayout.CENTER, button);
frame.setSize(300,300);
frame.setVisible(true);
}
}
▶ pull 방식으로 코드 바꾸기
이때까지는 옵저버가 요청하지 않아도 주제가 모든 옵저버에게 보내는(push) 방식이었는데, 옵저버가 주제로부터 데이터를 당겨오는(pull) 방식을 사용해도 됨
사실상 push VS pull을 선택하는 일은 구현방법의 문제이긴 하지만, 대체로는 옵저버가 필요한 데이터를 골라 가져가도록 만드는 것이 더 좋음!
주제에서 알림 보내기 - update의 인자 없애기
public void notifyObservers() {
for (Observer observer : observers) {
// observer.update(value);
observer.update();
}
}
옵저버에서 알림 받기 - update 매개변수 삭제
// 옵저버의 인터페이스
public interface Observer {
// public void update(float temp, float humidity, foat pressure);
public void update();
}
// 옵저버 구상클래스에서 update 구현부
//public void update(Observable o, Object arg) {
// this.value = (int) arg;
// display();
//}
public void update() {
this.temperature = weatherData.getTemperature();
this.humidity = weatherData.getHumidity();
display();
}
'Web Tech > Design Pattern' 카테고리의 다른 글
[ 헤드퍼스트 디자인패턴 - 11/06 ] Chap1. 디자인 패턴 소개와 전략패턴 (0) | 2022.11.06 |
---|