관찰자 패턴
관찰하고자하는 객체의 상태변화를 통보 받고자 할때 사용
MVC 패턴: 보통 모델과 뷰는 관찰자 패턴으로 설계된다.
목표: 관찰 대상객체의 measurementsChanged 메소드의 구현
class WeatherData{ ...//관찰자들 public void measurementsChanged(){ //this의 객체 변화가 발생하면 관찰자 들에게 해당 정보를 전달한다. //새로운 형태의 관찰자에도 맞추어 확장가능해야한다. } }
구현시도1. 관찰자 마다 update 적용
public void measurementsChanged(){ float temperature = getTemperature(); float humidity = getHumidity(); float pressure = getPressure(); //3종류 observer 각각 update 적용 currentConditionsDisplay.update(temperature, humidity, pressure); statisticDisplay.update(temperature, humidity, pressure); forecastDisplay.update(temperature, humidity, pressure); }
[문제점]
- observer 추가시 코드 수정이 불가피 : 확장성X
- update 조건의 수정시 코드 수정 불가피: 유연성X
- 코드중복, 긴 메서드
- 추상화(=다형성보장)하여 확장성과 유연성을 보장하고 코드중복을 줄여야한다. -> * Has-a이용
구현시도2. *HAS-A 이용
- 언제든 관찰을 중단할수있어야한다.
- 관찰대상자와 관찰자 간 1:N 관계를 맺고 자동으로 데이터변동을 통보해준다.
다형성을 위한 Subject, Observer 인터페이스!! -> 확장성,유연성,코드중복 제거
Observer나 subject의 수정은 서로에게 영향을 주지 않음
ConcreteSubject ,ConcreteObserver 등 새로운 구현체를 확장하기 좋음
방법2를 적용하여 관찰자패턴이 구현되어있다.
예제 Weather )
[추가사항]: 캡슐화를 위한 DisplayElement가 ConcreteObserver(CurrentConditionDisplay)에 포함(HAS-A)되어있다.
[코드] : 위 UML 설계
구현코드
//<<Subject>> //Dependency: <<observer>> 추상타입 의존에 의한 약한 의존관계 public interface Subject { void registerObserver(Observer o); void removeObserver(Observer o); void notifyObservers(); }
//ConcreteSubject: WeatherData //Dependency: <<WeatherData>> 상속(일반화) // <<observer>> has-a 객체 리스트 사용, 추상타입 의존에 의한 약한 의존관계 public class WeatherData implements Subject { /** 기본 ArrayList 중복 검사가 필요하면 -> Set 사용 가능 update 순서를 조율 -> 우선순위 큐 사용 가능 */ private List<Observer> observers = new ArrayList<>(); private float temperature; private float humidity; private float pressure; @Override public void registerObserver(Observer o) { if(o!=null) observers.add(o); } /** remove는 equals 비교하여 삭제조치 -> 별다른 재정의 없으면 Object equals 사용 -> Object equals는 주소기반 비교 -> 상태기반으로 삭제하고싶으면? "재정의 필요" */ @Override public void removeObserver(Observer o) { observers.remove(o); } @Override public void notifyObservers() { //관찰자들 업데이트 observers.forEach(o->o.update(temperature,humidity,pressure)); } public void measurementChanged() { notifyObservers(); //notifyObservers 자동실행 } public void setMeasurement(float temperature, float humidity, float pressure) { this.temperature = temperature; this.humidity = humidity; this.pressure = pressure; measurementChanged(); //measurementChanged 자동 실행 } }
- private List
observers = new ArrayList<>() 설명 중요! - removeObserver(Observer o) method 설명중요!
//<<Observer>> public interface Observer { void update(float temperature, float humidity, float pressure); }
//ConcreteObserver: CurrentConditionDisplay //Dependency: <<observer>> 구현(특수화) public class CurrentConditionDisplay implements Observer { private float temperature; private float humidity; private float pressure; public void display() { //update method의 새정보를 활용하는 method System.out.println("===최신 날씨 정보==="); System.out.printf("현재온도: %.2f%n", temperature); System.out.printf("현재습도: %.2f%n", humidity); System.out.printf("현재기압: %.2f%n", pressure); } @Override public void update(float temperature, float humidity, float pressure) { this.temperature = temperature; this.humidity = humidity; this.pressure = pressure; display(); //display 자동 실행 } }
활용코드
//Main 클라이언트가 사용할 실행 클래스 public class WeatherApp { public static void main(String[] args) { WeatherData weatherData = new WeatherData(); //Concrete Subject: 관찰 대상자 CurrentConditionDisplay currentConditionDisplay = new CurrentConditionDisplay(); //Concrete Observer: 관찰자 weatherData.registerObserver(currentConditionDisplay); //관찰대상자에게 관찰자 등록 //weatherData.registerObserver(new StatisticDisplay()); //다른종류의 관찰자를 추가 하는 경우 weatherData.setMeasurement(30, 65, 30.4f); //관찰대상자 상태변경1! //해당 데이터로 관찰자 currentConditionDisplay -> display()실행 weatherData.setMeasurement(28, 55, 29.2f); //관찰대상자 상태변경2! //해당 데이터로 관찰자 currentConditionDisplay -> display()실행 weatherData.removeObserver(currentConditionDisplay); //관찰대상자로부터 관찰자 삭제 } }
subject가 observer에게 변경점을 통보할 때 pull 이나 push 방식을 사용한다.
PULL
update method는 변경사실이 있다는 것과 객체 자체를 전달 받는다.
관찰자는 받아온 객체를 활용하여 원하는 데이터를 직접 호출해 사용한다.
긴밀한 연결: PULL 방식은 Subject와 Observer의 강한 의존관계를 갖는다.
//ConcreteSubject: WeatherData public class WeatherData implements Subject { private List<Observer> observers = new ArrayList<>(); ... @Override public void notifyObservers() { //관찰자들 업데이트 observers.forEach(o->o.update(this)); //PULL 방식: 객체 자체를 전달 } }
//ConcreteObserver: CurrentConditionDisplaypublic class CurrentConditionDisplay implements Observer { @Override public void update(WeatherData w) { temperature = w.getTemperature(); }}
[특징]
Subject 측면에서 더 편리하다: Subject는 객체를 통째로 넘기면 되기 때문, 데이터 활용은 Observer에게 위임
데이터 활용의 다양성이 보장된다: push방식보다 자유롭게 객체 데이터를 활용할수있다.
다중 쓰레드 환경에서 위험하다: pull 방식을 사용하면 중간에 상태변화가 일어나 문제를 발생시킬 위험이 있다.
Subject 와 Observer의 tight-coupling: Observer는 Subject 객체의 깊은 활용 정보까지 다 알아야한다.
PUSH
update method는 파라미터를 전달 받는다.
관찰자는 받아온 파리미터를 사용한다.
예제 Weather) 가 push방식
낮은 의존관계
[특징]
Obsever 측면에서 더 편리하다: Obsever는 넘어온 정보만 활용하면 되기때문
필요없는 정보까지 넘겨받는 상황이 있다.
한 관찰자가 여러 종류의 관찰대상을 관찰하는경우가 있다.
같은 종류의 관찰대상을 여러 개 관찰하는 경우
push
update(ID, Data) ID같은 데이터 구분 방법이 필요하다.
pull
update(ID, Subject) 같은 종류라면 ID가 필요하다.
다른 종류의 여러 관찰대상을 관찰하는 경우
push
다중정의나 메서드 이름을 각각 구분하여 해결
다중 정의시 ID같은 데이터 구분 방법이 필요하다.
pull
전달받는 객체로 구분이 가능하다.
관찰 대상의 상태변화의 유효함 검사는 관찰자가 담당한다.
관찰 대상은 상태변화가 일어나면 유효함에 상관없이 일단 관찰자에게 전달하고 관찰자가 유효검사를 담당
연쇄적 통보가 발생하는 형태로 설계되는 것은 바람직하지 않다.
- 관찰대상이 관찰자에게 통보하고 해당 관찰자가 다른 관찰자의 관찰대상이 되어 다른 관찰자에게 통보하는 형태
- 관찰대상이 관찰자에게 통보하고 해당 관찰자가 통보한 관찰대상의 상태를 수정하여 통보가 반복 되는 형태 (무한 반복될 수 있음)
관찰자에서 관찰 대상자의 상태를 수정하는것은 신중하게 고려해야한다.
패턴의 장점
장점 • 1대 N 관계를 느슨(push의 경우)하게 연결하여 주며, 여러 관찰자에게 동일 데이터를 효과적으로 전달할 수 있음 -> 다형성,편의성
• 관찰대상에 대한 수정 없이 새로운 종류의 관찰자를 추가할 수 있으며, 동적으로 관찰자를 추가 및 제거할 수 있음 -> 확장성,동적처리
• 관찰대상은 관찰자가 update 메소드를 가지고 있다는 것 외에는 알 필요가 없음 -> 느슨한 의존관계o
'개발 일지 > 디자인패턴 & 아키텍쳐' 카테고리의 다른 글
생성패턴 (0) | 2021.12.29 |
---|---|
장식자 패턴(Decorator) (0) | 2021.12.29 |
전략패턴(Strategy Pattern) (0) | 2021.12.29 |
[번외] UML 이해하기 (0) | 2021.12.29 |
OOP 설계 원리 (0) | 2021.12.29 |