Reference
📢 공부한 내용을 바탕으로 작성하였습니다. 내용에 문제가 있다면 댓글 또는 메일 jnw__@naver.com 주시면 확인 후 빠른 수정하겠습니다. !
✨ Observer Pattern : 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체에게 연락이 가고 자동으로 내용이 갱신되는 방식으로 일대다 (one-to-many) 의존성을 정의
- 일반적으로
주제 인터페이스
와옵저버 인터페이스
가 들어있는 클래스 디자인으로 구현
데이터가 업데이트 될 때, 디스플레이를 업데이트 하는 코드를 작성해보자.
PLUS
- 확장성 : 새로운 디스플레이가 얼마든지 추가되어도 되게 고려해보자.
코드 문제점 찾아보기
public class WeatherData {
// 인스턴스 변수 선언
public void measurementChanged() {
// 게터 메소드를 호출하여 최신 측정값을 가져온다.
float temp = getTemperature();
float humidity = getHumidity();
float pressure = getPressure();
// 각 디스플레이를 갱신한다.
currentConditionsDisplay.update(temp, humidity, pressure);
statisticsDisplay.update(temp, humidity, pressure);
forecastDisplay.update(temp, humidity, pressure);
// ...
}
}
a ) 디스플레이가 추가될 때 마다 update
코드를 넣어줘야 한다. 캡슐화
b ) 디스플레이 항목과 데이터를 주고 받을 때 공통된 인터페이스를 사용해야 한다.
c ) 실행 중에 디스플레이를 더하거나 빼기 어렵다.
✨ Subject Interface 와 Observer Interface 로 구성된다. Observer Interface 를 구현한 객체는 모두 Observer Class가 된다.
- 각 옵저버는 특정 주제(Subject)에 등록하여 연락을 받을 수 있다.
💍 객체들이 상호작용할 수 있지만, 서로를 잘 모르는 관계
- 느슨한 결합을 사용하면 유연성이 좋아진다.
옵저버 패턴에서는
✨ 느슨하게 결합하는 디자인은 변경사항이 생겨도 유연하게 처리할 수 있는 시스템을 구축할 수 잇게 해준다.
- 객체 사이의 상호의존성을 최소화하기 때문
public interface Subject{
//Observer 를 인자로 받아 등록, 제거하는 함수
public void registerObserver(Observer o);
public void removeObserver(Observer o);
// 등록된 Observer에 연락을 돌리는 함수
public void notifyObservers();
}
public interface Observer{
// 정보가 변경되었을 때 옵저버에게 전달되는 상태값
public void update(float temp, float humidity, float pressure);
}
// 모든 Display들이 구현할 interface
// Display 항목을 화면에 표시할 때 메소드 호출
public interface DisplayElement{
public void display();
}
public class WeatherData implements Subject{
private List<Observer> observers;
private float temperature;
private float humidity;
private float pressure;
public WeatherData(){
observers = new ArrayList<Observer>();
}
// 상태 변화를 알려줄 대상 추가
public void registerObserver(Observer o){
observers.add(o);
}
// 상태 변화를 알려줄 대상에서 제거
public void removeObserver(Observer o){
observers.remove(o);
}
// Observer에게 상태 변화를 알려주는 함수
public void notifyObservers(){
for (Observer observer : observers){
observer.update(temperature, humidity, pressure);
}
}
// 갱신된 측정값을 받으면 옵저버에게 알림
public void measurementsChanged(){
notifyObservers();
}
public void setMeasurements(float temperature, float humidity, float pressure){
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
}
// Display 요소는 Observer 객체이면서 DisplayElement를 구현한다.
public class CurrentConditionsDisplay implements Observer, DisplayElement{
private float temperature;
private float humidity;
private WeatherData weatherData;
// 생성자에 Subject 가 전달되고, 이를 이용해 해당 Display를 Observer 로 등록
public CurrentConditionsDisplay(WeatherData weatherData){
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
// update가 호출되면 온도와 습도가 저장되고 display 함수가 호출됨
public void update(float temperature, float humidity, float pressure){
this.temperature = temperature;
this.humidity = humidity;
display();
}
public void display(){
System.out.println("현재 상태: 온도 " + temperature + "F, 습도 " + humidity + "%");
}
}
public class WeatherStation{
public static void main(String[] args){
// WeatherData 객체 생성
WeatherData weatherData = new WeatherData();
// Display 생성자에 Subject를 전달
CurrentConditionDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);
// 새로운 기상값이 관측되었다고 가정
weatherData.setMeasurements(80, 65, 30.4f);
}
}
🧸 Subject 의 데이터를 Observer가 필요할 때 마다 당겨오는 방식
- 기존
update
함수의 parameter 를 제거하고 Subject의 게터 메소드를 활용
Subject에서 알림 보내기 : notifyObservers()
public void notifyObservers(){
for (Observer observer : observers){
observer.update();
}
}
Observer에서 알림받기 : update()
public interface Observer{
public void update();
}
public void update() {
// subject의 게터 메소드 활용
this.temperature = weatherData.getTemperature();
this.humidity = weatherData.getHumidity();
display();
}
다음 설명과 같은 요구사항이 있다.
위와 같은 요구사항을 만족하기 위해 observer 패턴을 적용하려고 한다.
(1) observer pattern을 사용한 class diagram을 개략적으로 그려 보세요.
-> 이미지 삽입
(2) observer pattern을 사용하여 설계한 코드를 자바 또는 C++ 으로 구현해 보세요.
// HWSubject.java
public interface HWSubject {
public void registerObserver(HWObserver o);
public void removeObserver(HWObserver o);
public void notifyObservers();
public void detectChange(int s);
}
// HWObserver.java
public interface HWObserver {
public void update();
public void changeState();
}
// HWConcreteSubject.java
import java.util.ArrayList;
import java.util.List;
public class HWConcreteSubject implements HWSubject {
private List<HWObserver> observers;
private int s;
public HWConcreteSubject(){
observers = new ArrayList<HWObserver>();
}
@Override
public void registerObserver(HWObserver o) {
observers.add(o);
// TODO Auto-generated method stub
}
@Override
public void removeObserver(HWObserver o) {
observers.remove(o);
// TODO Auto-generated method stub
}
@Override
public void notifyObservers() {
for(HWObserver observer : observers) {
observer.update();
}
// TODO Auto-generated method stub
}
@Override
public void detectChange(int s) {
this.s = s;
this.notifyObservers();
// TODO Auto-generated method stub
}
public int getState() {
return s;
}
}
// HWConcreteObserverA1.java
import java.util.List;
public class HWConcreteObserverA1 implements HWObserver {
private int s;
HWConcreteSubject subject;
public HWConcreteObserverA1(HWConcreteSubject concreteSubject) {
this.subject = concreteSubject;
concreteSubject.registerObserver(this);
}
public void setState(int s) {
this.s = s;
changeState();
}
@Override
public void update() {
// TODO Auto-generated method stub
this.s = subject.getState();
display();
}
@Override
public void changeState() {
this.subject.detectChange(this.s);
// TODO Auto-generated method stub
}
public void display(){
System.out.println("현재 값: " + s);
}
}
(3) 초기에 a1, a2, a3의 state는 각각 1, 2, 3이고, c1의 state를 5로 변경 후 각 state를 출력하는 client 코드를 작성해 보세요.
(4) 초기에 a1, a2, a3의 state는 각각 1, 2, 3이고, c2의 state를 5로 변경 후 각 state를 출력하는 client 코드를 작성해 보세요.
(5) 초기에 a1, a2, a3의 state는 각각 1, 2, 3이고, c3의 state를 10로 변경 후 각 state를 출력하는 client 코드를 작성해 보세요.
public class HWClientObserver {
public static void main(String[] args) {
HWConcreteSubject subject = new HWConcreteSubject();
HWConcreteObserverA a1 = new HWConcreteObserverA(subject,1);
HWConcreteObserverA a2 = new HWConcreteObserverA(subject,2);
HWConcreteObserverA a3 = new HWConcreteObserverA(subject,3);
a1.setState(5);
a2.setState(5);
a3.setState(10);
}
}
(6) 조건 변경