학습일지/Language

[Design Pattern] Observer

inspirit941 2020. 12. 17. 07:06
반응형

Observer 패턴

한 객체의 상태가 바뀌면, 해당 객체에 의존하는 다른 모든 객체에게 notice 전송 + 자동으로 내용이 갱신되는 one to many 방식의 의존성을 정의한 객체.

스크린샷 2020-12-11 오후 1 30 03

Subject 객체의 bCheck 변수 - 해당 객체가 변경되었는지 여부를 알리는 boolean.

  • bCheck가 false이면, Subject 객체에 의존하는 Observer 객체에게 값 변경이 있음을 알린다.
  • Observer 객체는 update() 메소드로 Subject값이 어떻게 변경되었는지 확인하고, 변경된 값에 대응되는 메소드를 실행한다.

자바의 Observer 내장패턴 사용하기

Push방식, Pull방식 모두 사용가능함.

  • java.util.Observer 인터페이스 구현 후 Observable 객체의

    • addObserver() : Observer 목록에 추가.
    • deleteObserver() : Observer 목록에서 삭제.
  • java.util.Observable을 상속받은 클래스에서 setChanged() 메소드 호출 시, 객체의 상태가 바뀌었음을 세팅

    • notifyObservers() / notifyObserver(Object arg) 호출.
  • Observer 객체는 update(Observable o, Object arg) 메소드를 구현하는 걸로 변경안내를 받을 수 있다.

    • Observable는 연락을 보낸 객체
    • Object arg는 notifyObserver(Object arg)에서 파라미터로 정의된 객체값임.
// Observable 객체. 상태 변화가 발생할 경우 Observer에 알림을 보내는 객체.
public class PlayController extends Observable {
    private boolean bPlay; // 실행여부

    public PlayController () {

    }

    // 데이터를 전달받아 플래그값을 변경하고,
    // 데이터가 변경되었음을 Observer에게 알린다.
    public void setFlag(boolean bPlay) {
        this.bPlay = bPlay;
        // 값 변경 = 아래 두 개의 메소드 실행하면 됨.
        // 별도로 구현할 내용 없음.
        setChanged()
        notifyObservers();
    }

    public boolean getFlag() {
        return bPlay;
    }
}

// Observer 객체 생성
public class Playable implements Observer {
    // Observer로 등록할 객체
    Observable observable;
    private boolean bPlay;

    public Playable(Observable o) {
        // 생성 시 Observable 변수에 객체 할당
        this.observable = o;
        // 자신을 observer로 등록.
        observable.addObserver(this);
    }

    @Override
    public void update(Observable o, Object arg) {
        (if o instanceof PlayController) {
            PlayController controller = (PlayController) o;
            this.bPlay = controller.getFlag();
            // 필요한 로직 수행.
            doAction();
        }
    }

    public void doAction() {
        if (bPlay) System.out.println("프로그램 시작");
        else System.out.println("프로그램 종료");
    }
}

public class TestObservationPattern {
    public static void main(String[] args){
        PlayController controller = new PlayController();

        Playable observer = new Playable(controller);

        System.out.println("클래스 일시정지");
        controller.setFlag(false); // observer에서 프로그램 종료를 콘솔창에 반환
        System.out.println("클래스 재시작");
        controller.setFlag(true); // observer에서 프로그램 시작을 콘솔창에 반환
    }
}

cf. Observable 객체는 JAVA 9부터 Deprecated 되었음.

문제점

  1. 재사용성.
    • Observable은 클래스로 정의되어 있음. 즉 subclass 형태로 구현해야 하는데, 이미 다른 클래스를 상속받은 상태라면 Observable 객체를 상속받을 수 없는 구조.
  2. Observable의 핵심 클래스를 외부에서 호출할 수 없다.
    • setChanged() 메소드가 protected로 정의되어 있음. 즉 Observable을 상속받은 서브클래스만 setChanged()를 호출할 수 있다.
  3. Observable 클래스는 Serialize가 불가능하다. Observable 클래스는 Serializer 인터페이스의 구현체가 아니기 때문
  4. 모든 Observer의 구현로직이 동일하다. instanceof로 Observable 객체를 확인하고, 해당 객체의 타입으로 cast하는 방식의 update메소드.
  5. Not Thread Safe. notification can occur in different orders / on different threads.
  6. 너무나도 제한된 쓰임새. (Not provide a rich Enough event model for applcation). 무언가가 바뀌었다는 사실은 전달하지만, 어떤 게 바뀌었는지는 전달하지 못하는 객체 구조

java.beans 패키지의 Listener를 사용하라고 권장하고 있다.

PropertyChangeListener나 PropertyChangeEvent와 같은...

https://stackoverflow.com/questions/46380073/observer-is-deprecated-in-java-9-what-should-we-use-instead-of-it

직접 구현할 경우

public interface Publisher {
    public void addObserver(Observer o);
    public void deleteObserver(Observer o);
    public void notifyObserver();
}
public interface Observer {
    public void update(boolean play);
}

public class PlayController implements Publisher {
    private List<Observer> observers = new ArrayList<>();
    private boolean play;

    @Override
    public void addObserver(Observer observer) { observers.add(observer) }

    @Override
    public void deleteObserver(Observer observer) {
        // 해시맵 쓰면 더 효율적일 거 같은데
        int index = observer.indexOf(observer);
        observers.remove(index);
    }

    @Override
    public void notifyObservers() {
        for (int i=0; i < observers.size(); i++) { 
            observers.get(i).update(play);
        }
    }

    public void setFlag(boolean play) {
        this.play = play;
        notifyObservers();
    }
    public void getFlag() { return play; }   
}

public class Playable implements Observer {
    private boolean bPlay;

    // Publisher를 생성자에 파라미터로 추가해서, 객체 생성 시 바로 등록되도록 만들 수도 있다.

    @Override
    public void update(boolean play) {
        this.bPlay = bPlay;
        doAction();
    }

    public void doAction() {
        if (bPlay) System.out.println("프로그램 시작");
        else System.out.println("프로그램 종료");
    }
}

public class TestObservationPattern {
    public static void main(String[] args){
        PlayController pager = new PlayController();

        Playable observer = new Playable(controller);

        // 등록
        pager.addObserver(observer); 
        // 메시지 등록
        pager.setFlag(false); // 콘솔에 "프로그램 종료" 출력
        // 삭제
        pager.deleteObserver(observer);
        // 메시지 등록
        pager.setFlag(true);
    }
}

안드로이드의 View / Button 등, 위젯의 이벤트를 받을 때 주로 쓰임.

  • 버튼에는 클릭 이벤트가 OnClickListener 인터페이스로 구성돼 있다. 즉 버튼이라는 객체는 Publisher, OnClickListener는 Observer가 됨. 클릭이라는 상태 변경이 발생할 경우 OnClickListener로 알려주는 것
반응형