일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- ncs
- 재택근무
- js
- 한번에끝내는JavaSpring웹개발마스터초격차패키지Online강의
- 리눅스
- 직장인인강
- DesignPattern
- String
- 데이터베이스
- DB
- 자바기초
- 웹
- 직장인자기계발
- 패캠챌린지
- Spring
- 스프링
- 자바예제
- 자바연습문제
- 자바기본
- 디자인패턴
- 디자인
- java
- linux
- 자바
- java기초
- javabasic
- 패스트캠퍼스
- 국비
- 한번에끝내는JavaSpring웹개발마스터초격차패키지Online
- 패스트캠퍼스후기
- Today
- Total
FIF's 코딩팩토리
옵저버 패턴(Observer Pattern) 정리 본문
옵저버 패턴(OBSERVER PATTERN) 정의한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들한테 연락이 가고 자동으로 내용이 갱신되는 방식으로 일대다(one-to-many) 의존성을 정의한다. |
옵저버 패턴을 구현하는 방법에는 여러가지가 있지만, 대부분 상태를 저장하고 있는 주제 인터페이스를 구현한 하나의 주제객체와 주제객체에 의존하고 있는 옵저버 인터페이스를 구현한 여러 개여 옵저버객체가 있는 디자인을 바탕으로 한다.
데이터 전달방식 2가지
1. 주제객체에서 옵저버로 데이터를 보내는 방식(Push 방식)
2. 옵저버에서 주제객체의 데이터를 가져가는 방식(Pull 방식)
옵저버 패턴 클래스 다이어그램
디자인 원칙서로 상호작용을 하는 객체 사이에서는 가능하면 느슨하게 결합하는 디자인을 사용해야 한다. |
1. 옵저버 패턴은 주제와 옵저버가 느슨하게 결합되어 있는 객체 디자인을 제공한다.
2. 주제가 옵저버에 대해서 아는 것은 옵저버가 특정 인터페이스(Observer Interface)를 구현 한다는 것 뿐이다.
3. 옵저버는 언제든지 새로 추가할 수 있다.(주제는 Observer Interface를 구현하는 객체 목록에만 의존하기 때문이다.)
4. 새로운 형식의 옵저버를 추가하려 해도 주제를 전혀 변경할 필요가 없다.(새로운 클래스에서 Observer Interface만 구현해주면 되기 때문이다.)
5. 주제나 옵저버가 바뀌더라도, 서로에게 영향을 주지 않는다. 그래서 주제와 옵저버는 서로 독립적으로 재사용할 수 있다.
느슨하게 결합하는 디자인을 사용하면 변경 사항이 생겨도 무난히 처리할 수 있는 유연한 객체지향 시스템을 구축할 수 있다.(객체 사이의 상호 의존성을 최소화 할 수 있기 때문이다.)
문제 발생
날씨 데이터를 가지고 있는 회사와 데이터를 연동하여 여러종류의 디스플레이에 날씨 데이터를 출력해줘야 하는 일이 생겼다고 가정하자.
제공받은 객체와 각 메소드의 역할
getTemperature() : 온도
getHumidity() : 습도
getPressure() : 기압
measurementsChanged() : 새로운 기상 측정 데이터가 나올 때 마다 자동으로 호출되는 부분
대략적인 구상
measurementsChanged 메소드 안의 Display update 메소드들이 구체적인 구현에 맞춰 코딩이 되있기 때문에, 프로그램을 고치지 않고는 다른 디스플레이 항목을 추가/제거 할 수 없다.
향후에 바뀔 수 있는 부분은 캡슐화해서 분리하여야 한다.
모든 디스플레이에 효과적으로 Weather상태를 알려줄 수 있는 방법이 필요하다.
public class WeatherData{
// 인스턴스 변수들
public void measurementsChanged(){ //새로운 데이터 세팅시 갱신되는 메소드
float temp = getTemperature();
float humidity = getHumidity();
float pressure = getPressure();
//3개의 디스플레이가 있다고 가정
currentCondirionsDisplay.update(temp, humidity, pressure); //디스플레이 갱신
statisticsDisplay.update(temp, humidity, pressure); //디스플레이 갱신
forecastDisplay.update(temp, humidity, pressure); //디스플레이 갱신
}
//기타 메소드…
}
옵저버 패턴 구현하기
Subject.java
public interface Subject {
public void registerObserver(Observer o);
//위아래 메소드는 Observer를 인자로 받는다. 옵저버 등록/제거 역할
public void removeObserver(Observer o);
//주제 객체의 상태가 변경되었을 때 모든 옵저버들한테 알리기 위한 메소드
public void notifyObservers();
}
Oberser.java
public interface Observer {
//기상 정보가 변경되었을때 옵저버 한테 전달되는 상태 값
//이 인터페이스는 모든 옵저버 클래스에서 구현해야 한다.
//모든 옵저버는 update()메소드를 구현해야 한다.
public void update(float temp, float humidity, float pressure);
}
DisplayElement.java
public interface DisplayElement {
//디스플레이 항목을 화면에 표시해야 하는 경우 호출하는 메소드
public void display();
}
WeatherData.java
import java.util.ArrayList;
public class WeatherData implements Subject {
// Observer 객체들을 저장하기 위해 ArrayList추가
private ArrayList observers;
private float temperature;
private float humidity;
private float pressure;
public WeatherData() {
// ArrayList형 observer객체 생성(생성자)
observers = new ArrayList();
}
//옵저버가 등록하면 목록 맨 뒤에 추가만 하면 된다.
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
//중요! 상태에 대해서 모든 옵저버들한테 알려주는 메소드
//모두 Observer인터페이스를 구현하는, 즉 update()메소드가 있는 객체들이므로 손쉽게 알려줄 수 있다.
@Override
public void notifyObservers() {
for (int i = 0; i < observers.size(); i++) {
Observer observer = (Observer) observers.get(i);
observer.update(temperature, humidity, pressure);
}
}
//옵저버가 탈퇴를 신청하면 목록에서 뺀다.
@Override
public void removeObserver(Observer o) {
int i = observers.indexOf(o);
if (i >= 0) {
observers.remove(i);
}
}
//기상 스테이션으로 부터 갱신된 측정치를 받으면 옵저버들한테 알린다.
public void measurementsChanged() {
notifyObservers();
}
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
}
CurrentConditions.java
public class CurrentConditionsDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
private Subject weatherData;
public CurrentConditionsDisplay(Subject weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
@Override
public void update(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
display();
}
//가장 최근에 얻은 기온과 습도 출력
@Override
public void display() {
System.out.println("Current conditions: " + temperature + "F degree and" + humidity + "% humidity");
}
}
WeatherStation.java
public class WeatherStation {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);
//새로운 기상 측정값이 들어온 것처럼 만든다.
weatherData.setMeasurements(80, 65, 30.4f);
weatherData.setMeasurements(82, 70, 29.2f);
weatherData.setMeasurements(78, 92, 29.2f);
}
}
자바에 내장된 옵저버 패턴을 사용하여 구현하기
java.util.Observer 인터페이스와 java.util.Observable 클래스를 사용할 수 있다.
자바 내장 옵저버 패턴은 push, pull 방식 모두 사용 가능하다.
자바 내장 옵저버 패턴 클래스 다이어그램
이전에 구현 했던 것과 마찬가지로 java.util.Observer 인터페이스를 구현하고, java.util.Observable 객체의 addObserver()메소드를 호출하면 옵저버가 옵저버 목록에 추가되고 deleteObserver()를 호출하면 옵저버 목록에서 탈퇴된다.
연락을 돌리는 방법은 java.util.Observable를 상속받는 주제 클래스에서 setChanged() 메소드를 호출해서 객체의 상태가 바뀐걸 알린 후, notifyObservers() 또는 notifyObserver(Object args)메소드를 호출하면 된다.(인자값을 넣어주는 메소드는 push방식으로 쓰인다.)
옵저버 객체가 연락받는 방법은 update(Observable o, Object args) 메소드를 구현하면 된다.
Observable에는 연락을 보내는 주제 객체가 인자로 전달되고,
Object args 같은 경우는 notifyObservers(Object arg) 메소드에서 인자로 전달된 데이터 객체가 넘어온다.
WeatherData.java
public class WeatherData extends Observable{
private float temperature;
private float humidity;
private float pressure;
public void measurementsChanged(){
//상태가 바뀌었다는 플래그값을 바꿔줌.
this.setChanged();
//풀 방식을 사용해서 알림
this.notifyObservers();
}
//값이 세팅된다고 가정.
public void setMeasurementsChanged(float t, float h, float p){
this.temperature = t;
this.humidity = h;
this.pressure = p;
this.measurementsChanged();
}
public float getTemperature() {
return temperature;
}
public float getHumidity() {
return humidity;
}
public float getPressure() {
return pressure;
}
}
CurrentConditions.java
public class CurrentConditions implements Observer, DisplayElement{
private Observable observable;
private float temperature;
private float humidity;
public CurrentConditions(Observable observable) {
this.observable = observable;
this.observable.addObserver(this);
}
@Override
public void display() {
System.out.println("Current conditions : "+temperature+" , "+humidity);
}
@Override
public void update(Observable o, Object arg) {
if(o instanceof WeatherData){
WeatherData weatherData = (WeatherData) o;
this.temperature = weatherData.getTemperature();
this.humidity = weatherData.getHumidity();
this.display();
}
}
}
java.util.Observable의 단점
1. Observable은 클래스이기 때문에, 서브클래스를 만들어야 한다. 이미 다른 수퍼클래스를 확장하고 있는 클래스에 Observable의 기능을 추가할 수 없어 재사용성에 제약이 생긴다.
2. Observable 인터페이스라는 것이 없기 때문에, 자바에 내장된 Observer API하고 잘 맞는 클래스를 직접 구현하는 것이 불가능 하다.
3. java.util.Observable을 확장한 클래스를 쓸 수 있는 상황이라면 Observer API를 쓰는것도 괜찮겠지만, 상황에 따라선 직접 구현해야 할 수도 있다.
결론 : 둘 중, 어떤 방법을 쓰든 옵저버 패턴만 제대로 알고 있다면 그 패턴을 활용하는 API는 무엇을 쓰든간에 잘 활용할 수 있을 것이다.
'Back-End > Design Pattern(디자인 패턴)' 카테고리의 다른 글
싱글톤 패턴(Singleton Pattern) 정리 (0) | 2019.06.05 |
---|---|
데코레이터 패턴(Decorater Pattern) 정리 (0) | 2019.06.05 |
스트래티지 패턴(Strategy Pattern) 정리 (0) | 2019.06.04 |
팩토리 패턴(Factory Pattern) 정리 (0) | 2019.06.04 |
커멘드 패턴(Command Pattern) 정리 (0) | 2019.06.04 |