ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 디자인 패턴 (2) - 옵저버 패턴
    언어/디자인패턴 2023. 9. 8. 15:34

     

    1. 옵저버 패턴이란:

     

    옵저버(관찰자)들이 관찰하고 있는 대상자의 상태가 변화할 때,

    대상자가 관찰자에게 통지하고, 관찰자들은 알림을 받아 조치를 취하는 행동 패턴이다.

     

    다른 디자인 패턴들과 다르게 일대다의 의존성을 가진다. 주로 분산 이벤트 핸들링에 사용된다.

     

    - 옵저버 패턴의 흐름

     

    > 한개의 관찰 대상자(Subjet)와 여러개의 관찰자(Observer)로 이루어짐

    > Subject의 상태가 바뀌면 옵저버에 통보 

    > 통보 받은 Observer는 해당 값을 처리 

    > Observer들은 Subject 그룹에서 추가/삭제 될 수 있다.

     

    EX) MVC패턴 

    Model과 View의 관계가 옵저버 패턴과 같다. 하나의 Model에 복수의 View가 대응

     

    -* 발행자 코드 변경하지 않고 새 구독자 클래스를 도입 (OCP 준수)

    -* 런타임 시점에서 발행자와 구독 알림 관계 맺을 수 있다. 

    -* 상태를 변경하는 객체변경을 감지하는 객체의 관계를 느슨하게 유지할 수 있다.

     

    -> 핵심은 대상과 옵저버(리스너)의 관계에서 대상에 옵저버를 포함시키는 것 (add를하던, 옵저버 만들면서 넣던)

    -> subject에 특정 변화가 있으면 옵저버들에게 알리기 (일종의 콜백)  -> 옵저버는 호출받으면 하고싶은대로 알아서 처리 

        *subject의 특정 변화는 스스로 만드는게 아님! 변화는 외부에서 주어지는 것.. 당연하게도

    출처: https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%EC%98%B5%EC%A0%80%EB%B2%84Observer-%ED%8C%A8%ED%84%B4-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EB%B0%B0%EC%9B%8C%EB%B3%B4%EC%9E%90#%EC%98%B5%EC%A0%80%EB%B2%84_%ED%8C%A8%ED%84%B4_%EA%B5%AC%EC%A1%B0

    - 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하게 만들어짐 

     

     

    참고자료: 

    헤드퍼스트 디자인패턴 (한빛미디어)

     

    https://inpa.tistory.com/

     

    Inpa Dev 👨‍💻

    성장 욕구가 가파른 초보 개발자로서 공부한 내용을 쉽게 풀어쓴 기술 개발자 블로그를 운영하고 있습니다.

    inpa.tistory.com

     

Designed by Tistory.