-
디자인 패턴 (2) - 옵저버 패턴언어/디자인패턴 2023. 9. 8. 15:34
1. 옵저버 패턴이란:
옵저버(관찰자)들이 관찰하고 있는 대상자의 상태가 변화할 때,
대상자가 관찰자에게 통지하고, 관찰자들은 알림을 받아 조치를 취하는 행동 패턴이다.
다른 디자인 패턴들과 다르게 일대다의 의존성을 가진다. 주로 분산 이벤트 핸들링에 사용된다.
- 옵저버 패턴의 흐름
> 한개의 관찰 대상자(Subjet)와 여러개의 관찰자(Observer)로 이루어짐
> Subject의 상태가 바뀌면 옵저버에 통보
> 통보 받은 Observer는 해당 값을 처리
> Observer들은 Subject 그룹에서 추가/삭제 될 수 있다.
EX) MVC패턴
Model과 View의 관계가 옵저버 패턴과 같다. 하나의 Model에 복수의 View가 대응
-* 발행자 코드 변경하지 않고 새 구독자 클래스를 도입 (OCP 준수)
-* 런타임 시점에서 발행자와 구독 알림 관계 맺을 수 있다.
-* 상태를 변경하는 객체와 변경을 감지하는 객체의 관계를 느슨하게 유지할 수 있다.
-> 핵심은 대상과 옵저버(리스너)의 관계에서 대상에 옵저버를 포함시키는 것 (add를하던, 옵저버 만들면서 넣던)
-> subject에 특정 변화가 있으면 옵저버들에게 알리기 (일종의 콜백) -> 옵저버는 호출받으면 하고싶은대로 알아서 처리
*subject의 특정 변화는 스스로 만드는게 아님! 변화는 외부에서 주어지는 것.. 당연하게도
- Subject (주제) : 옵저버 등록, 탈퇴에 관한 책임 정의 인터페이스
- ConcreteSubject: -Subject 인터페이스 구현 (옵저버 등록/수정 관련 책임 수행 가능)
- Subject 인페이스로 부터 받은 것 외에 메서드나 상태는 주제에 알맞게 됨
( 주제의 상태 알아내는 세터 개터)
- 내부적으로 여러 옵저버를 담을 수 있는 List나 Map등으로 가지고 있음
- Observer: - 옵저버가 될 가능성이 있는 객체가 구현할 인터페이스
- 주제에 변화를 감지할 수 있는 메서드 가짐
- ConcreteObserver: 옵저버 구현 -> Subject로 내려받은 데이터 각자 사용
* 옵저버 패턴에서는 Subject가 상태를 저장하고 제어 -> 상태가 들어있는 객체는 하나
옵저버는 상태를 사용하지만 소유할 수는 없다! -> Subject에 의존적
2. 가상 날씨 스테이션 예제
- 가상 스테이션에서 데이터를 읽고 -> WeatherData객체에 넘김
- WeatherData객체에서(Subject) -> 디스플레이 장비들(Observer)로 데이터 넘김
2.1 디자인 패턴 적용 전
public class WeatherData { //인스턴스 변수 public void measurementsChanged() { float temp = getTemp(); float humidity = getHumi(); float pressure = getPress(); currentConditionDisplay.update(temp,humidity,pressure); staticDisplay.update(temp,humidity,pressure); forecesDisplay.update(temp,humidity,pressure); } }
문제점 : - currentConditionDisplay등 구체적인 구현에 맞춰 코딩함 -> Display확장, 축소시 소스 수정해야함
- update()라는 공통 책임을 가지고 있음으로 인터페이스화 하는게 나음
> 전체적으로 OCP위반
> Display확장이라는 변화에 대해 인터페이스-캡슐화 설계 부족
2.2 디자인 패턴 적용 (옵저버)
- 인터페이스 목록
public interface DisplayElement { public void display(); } public interface Observer { public void update(float temp, float hum, float pre); } public interface Subject { public void registerObserver(Observer o); public void removeOberver(Observer o); public void notigyObserver(); }
- 구현 클래스
public class WeatherData implements Subjet{ private List<Observer> observers; private float temp; private float hum; private float pre; public WeatherData() { observers = new ArrayList<>(); } public void measurementsChanged() { this.notigyObserver(); //값 갱신시 옵저버에게 알림 } public void serMesurements(float tem, float hum, float pre) { this.hum=hum; this.temp = temp; this.pre = pre; this.measurementsChanged(); } @Override public void registerObserver(Observer o) { observers.add(o); } @Override public void removeOberver(Observer o) { observers.remove(o); } @Override public void notigyObserver() { observers.stream().forEach(ob->{ ob.update(temp, hum, pre); }); } } public class currentConditionDisplay implements Observer,DisplayElement { private float tem; private float hum; private WeatherData weather; //참조값 저장해두면, 탈퇴시 유용하대 public currentConditionDisplay(WeatherData we) { this.weather = we; we.registerObserver(this); } @Override public void display() { // TODO Auto-generated method stub } @Override public void update(float temp, float hum, float pre) { this.hum =hum; this.tem = tem; display(); } }
* 옵저버는 생성자로 구독할 주제를 받음
2.3 풀 방식으로 코드 수정해보기
// 알림보내기 public void notifyObserver(){ Observers.stream().forEach(ob -> { ob.update(); // 매개변수 없애기 }) } //알림 받기 public void update(){ this.tem = weatherData.getTemp(); this.hum = weatherData.getHum(); display(); }
3. 느슨한 결합의 위력
- 느슨한 결합은 객체들이 상호작용할 수는 있지만, 서로를 잘 모르는 관계이다. (클라이언트가 구체타입x 상위타입 의존)
- 느슨한 결합을 사용하면 유연성이 좋아빈다.
- 이를 옵저버 패턴에 적용하면 다음과 같다.
3.1 Subject는 옵저버가 특정 인터페이스를 구현한다는 사실만 안다.
옵저버의 구현 클래스가 무엇인지, 뭘 하는지 알 필요가 없다.
3.2 옵저버는 언제든 새로 추가 가능
Subjcet는 인터페이스에만 의존함 -> 새로운 옵저버 추가가 쉽다.
3.3 새로운 옵저버를 추가할 때 Subject를 변경할 필요가 없다.
3.4 Subject와 옵저버는 독립적으로 재사용이 가능하다.
3.5 Subject와 옵저버는 변화에 대해 서로 영향을 미치지 않는다.
- 느슨한 결합이므로, 주제나 옵저버 인터페이스만 구현한다는 조건만 만족하면 어떻게 고쳐도 상관 없음
4. 라이브러리 속 옵저버 패턴
- JFrame
public class SwingObserverExample { JFrame frame; public static void main(String[] args) { SwingObserverExample ex = new SwingObserverExample(); ex.go(); } private void go() { frame = new JFrame(); JButton button = new JButton("할 말?"); button.addActionListener(new AngelListener()); //버튼을 누르면 반응하는 천사와 악마 리스너 button.addActionListener(new DevilListener()); frame.show(); } } class AngelListener implements ActionListener{ @Override public void actionPerformed(ActionEvent e) { System.out.println("하지마"); } } class DevilListener implements ActionListener{ @Override public void actionPerformed(ActionEvent e) { System.out.println("go"); } }
* 디자인 원칙: 상호작용하는 객체 사이에는 가능하면, 느슨한 결합을 사용하자
> 느슨한 결합은 인터페이스에 대고 프로그래밍하는 원칙에 의해 달성 가능
5. 내장 Observe (java.util.Observer와 Observable)
public interface Observer { /** * This method is called whenever the observed object is changed. An * application calls an {@code Observable} object's * {@code notifyObservers} method to have all the object's * observers notified of the change. * * @param o the observable object. * @param arg an argument passed to the {@code notifyObservers} * method. */ void update(Observable o, Object arg); // Java.Util //Observable 구현체로부터 전달받은 새로운 데이터 갱신 }
public class Observable { private boolean changed = false; private Vector<Observer> obs; public Observable() { obs = new Vector<>(); } public synchronized void addObserver(Observer o) { if (o == null) throw new NullPointerException(); if (!obs.contains(o)) { obs.addElement(o); } } public synchronized void deleteObserver(Observer o) { obs.removeElement(o); } public void notifyObservers() { notifyObservers(null); } public void notifyObservers(Object arg) { Object[] arrLocal; synchronized (this) { if (!changed) return; arrLocal = obs.toArray(); clearChanged(); } for (int i = arrLocal.length-1; i>=0; i--) ((Observer)arrLocal[i]).update(this, arg); } public synchronized void deleteObservers() { obs.removeAllElements(); }
- Observer들 등록하거나 추가하는 작업은 Thread-safe하게 만들어짐
참고자료:
헤드퍼스트 디자인패턴 (한빛미디어)
'언어 > 디자인패턴' 카테고리의 다른 글
디자인 패턴 (7) - 커맨드 패턴 (0) 2023.09.12 디자인 패턴 (6) - 싱글톤 패턴 (0) 2023.09.11 디자인 패턴 (5) - 추상 팩토리 패턴 (0) 2023.09.11 디자인 패턴 (4) - 팩토리 패턴 (0) 2023.09.11 디자인패턴 (3) - 데코레이터패턴 (0) 2023.09.08