Observer 패턴

gang_shik·2022년 3월 17일
0

Observer 패턴

  • 관찰 대상의 상태가 변화하면 관찰자에게 알려줌
  • Observer 패턴은 상태 변화에 따른 처리를 기술할 때 효과적임

예제 프로그램

  • Observer : 관찰자를 나타내는 인터페이스
  • NumberGenerator : 수를 생성하는 오브젝트를 나타내는 클래스
  • RandomNumberGenerator : 랜덤으로 수를 생성하는 클래스
  • DigitObserver : 숫자로 수를 표시하는 클래스
  • GraphObserver : 간이 그래프로 수를 표시하는 클래스
  • Notifies는 NumberGenerator가 Observer에 통지함을 의미함

Observer 인터페이스

public interface Observer {
		public abstract void update(NumberGenerator generator);
}
  • Observer 인터페이스는 관찰자를 표현하는 인터페이스이고 구체적인 관찰자는 이 인터페이스를 구현함

  • update 메소드는 NumberGenerator 클래스에서 호출됨, 해당 클래스에서 나의 내용이 갱신되어 표시 쪽도 갱신해 주라는 것을 Observer에 전달하기 위한 메소드임

NumberGenerator 클래스

import java.util.ArrayList;
import java.util.Iterator;

public abstract class NumberGenerator {
		private ArrayList observers = new ArrayList(); // Observer를 저장
		public void addObserver(Observer observer) { // Observer를 추가
				observers.add(observer);
		}
		public void deleteObserver(Observer observer) { // Observer를 삭제
				observers.remove(observer);
		}
		public void notifyObservers() { // Observer에 알림
				Iterator it = observers.iterator();
				while (it.hasNext()) {
						Observer o = (Observer)it.next();
						o.update(this);
				}
		}
		public abstract int getNumber(); // 수를 취득함
		public abstract void execute(); // 수를 생성함
}
  • NumberGenerator 클래스는 수를 생성하는 추상 클래스임, 실제의 수의 생성(execute 메소드)과 수를 취득하는 부분(getNumber 메소드)은 하위 클래스에서 구현하도록 추상 메소드로 되어 있음

  • observers 필드는 NumberGenerator를 관찰하는 Observer를 보존하는 필드임

  • addObserver는 Observer를 추가하는 메소드이고, deleteObserver는 Observer를 삭제하는 메소드임

  • notifyObservers 메소드는 Observer 전원에 대해서 나의 내용이 갱신되었으므로 당신의 표시를 갱신해 주십시오라고 전함, 이 메소드 안에서는 observers 안의 Observer들 한 사람 한 사람의 update 메소드를 호출함

RandomNumberGenerator 클래스

import java.util.Random;

public class RandomNumberGenerator extends NumberGenerator {
		private Random random = new Random(); // 난수발생기
		private int number; // 현재의 수
		public int getNumber() { // 수를 취득함
				return number;
		}
		public void execute() {
				for (int i = 0; i < 20; i++) {
						number = random.nextInt(50);
						notifyObservers();
				}
		}
}
  • NumberGenerator의 하위 클래스이고, 난수를 생성함

  • random 필드에는 java.util.Random 클래스의 인스턴스가 저장되고, number 필드에는 현재의 난수값이 저장됨, getNumber 메소드는 number 필드의 값을 반환함

  • execute 메소드는 난수를 20개 생성하고, 그때마다 notifyObservers를 사용해서 관찰자에게 통지함

  • nextInt 메소드는 랜덤인 정수값을 반환함

DigitObserver 클래스

public class DigitObserver implements Observer {
		public void update(NumberGenerator generator) {
				System.out.println("DigitObserver:" + generator.getNumber());
				try {
						Thread.sleep(100);
				} catch (InterruptedException e) {
				}
		}
}
  • Observer 인터페이스를 구현하는 클래스로 관찰한 수를 숫자로 표시하기 위한 것임

  • update 메소드 안에서 인수로 주어진 NubmerGenerator의 getNumber 메소드를 사용해서 수를 취득하고 그것을 표시함(표시되는 모습을 잘 보이도록 Thread 사용)

GraphObserver 클래스

public class GraphObserver implements Observer {
		public void update(NumberGenerator generator) {
				System.out.print("GraphObserver:");
				int count = generator.getNumber();
				for (int i = 0; i < count; i++) {
						System.out.print("*");
				}
				System.out.println("");
				try {
						Thread.sleep(100);
				} catch (InterruptedException e) {
				}
		}
}
  • Observer 인터페이스를 구현하는 클래스임, 이 클래스는 관찰한 수를 간이 그래프로 표시함

Main 클래스

public class Main {
		public static void main(String[] args) {
				NumberGenerator generator = new RandomNumberGenerator();
				Observer observer1 = new DigitObserver();
				Observer observer2 = new GraphObserver();
				generator.addObserver(observer1);
				generator.addObserver(observer2);
				generator.execute();
		}
}
  • RandomNumberGenerator의 인스턴스를 한 개 만들고, 그 관찰자를 두 개 만듬

  • observer1은 DigitObserver, observer2는 GraphObserver의 인스턴스임

  • addObserver 메소드를 사용해서 관찰자를 등록한 후 generator.execute를 사용해서 수를 생성함


정리

Subject(관찰 대상자)의 역할

  • Subject는 관찰되는 대상을 나타냄

  • 관찰자인 Observer 역할을 등록하는 메소드와 삭제하는 메소드를 가지고 있음

  • 현재의 상태를 취득하는 메소드도 선언되어 있음

ConcreteSubject(구체적인 관찰 대상자)의 역할

  • ConcreteSubject는 구체적으로 관찰되는 대상을 나타냄

  • 상태가 변화하면 그것이 등록되어 있는 Observer 역할에 전함

Observer(관찰자)의 역할

  • Observer는 Subject 역할로부터 '상태가 변했습니다' 라고 전달 받는 역할을 함, 이를 위한 메소드가 update임

ConcreteObserver(구체적인 관찰자)의 역할

  • ConcreteObserver는 구체적인 Observer임, update 메소드가 호출되면 그 메소드 안에서 Subject 역할의 현재 상태를 취득

결론

  • 이 패턴 역시 클래스를 재이용 가능한 부품으로 만들 수 있음

  • Observer 패턴에서는 상태를 가지고 있는 ConcreteSubject 역할과 상태변화를 전달 받는 ConcreteObserver 역할이 등장함, 이 두 가지 역할을 연결하는 것이 인터페이스(API)인 Subject 역할과 Observer 역할임

  • RandomNumberGenerator 클래스는 현재 자신을 관찰하고 있는 것이 DigitObserver 클래스의 인스턴스인지 GraphObserver 클래스의 인스턴스인지 몰라도 상관없음, 그러나 observer 필드에 저장되어 있는 인스턴스들이 Observer 인터페이스를 구현하고 있다는 것을 알고 있음

  • 이 인스턴스들은 addObserver에서 추가된 것이므로 반드시 Observer 인터페이스를 구현하고 있으며 update 메소드를 호출할 수 있음

  • 그리고 DigitObserver 클래스는 자신이 관찰하고 있는 것이 RandomNumberGenerator 클래스의 인스턴스인지 다른 클래스의 인스턴스인지 신경 쓰지 않음, 단지 NumberGenertator의 하위 클래스의 인스턴스이고 getNumber 메소드를 가지고 있다는 것을 알고 있음

  • 여기서 반복적으로 나오는 것이 2가지 존재함

    • 추상 클래스나 인터페이스를 사용해서 구상 클래스로부터 추상 메소드를 분리함
    • 인수로 인스턴스를 전달할 때, 필드에서 인스턴스를 저장할 때에는 구상 클래스의 형태로 하지 않고 추상 클래스나 인터페이스의 형태로 해 둠

갱신을 위한 힌트 정보의 취득

  • NumberGenerator는 update 메소드를 사용해서 갱신되었다고 Observer에게 통지하고 있음, update 메소드의 인수로 주어지고 있는 것은 호출을 행한 NumberGenerator의 인스턴스 뿐임

  • Observer는 update 메소드 안에서 getNumber를 호출해서 필요한 정보를 얻어야함

  • 예제 프로그램 한정해서 update 메소드의 인수로 갱신된 수 그 자체를 제공해도 상관없음

  • 원래 update 메소드

    • void update(NumberGenerator generator);
    • Subject 역할만을 전달함, Observer는 Subject 역할로부터 필요에 따라 정보를 얻음
  • 수 자체 제공

    • void update(NumberGenerator generator, int number);
    • Subject 역할에 더해서 힌트 정보를 전달함, Observer Subject 역할로부터 정보를 얻는 시간을 줄일 수 있음
    • 힌트 정보 제공한다는 것이 Subject 역할이 Observer 역할의 처리 내용을 의식한다는 뜻
    • 예제 프로그램보다 복잡한 경우 Observer 역할에게 필요한 정보는 무엇인지 Subject 역할에게 알리는 것은 어려운 문제임, 그래서 어느 정도 힌트 정보를 update 메소드에게 제공하는 것을 고려할 수 있음
  • 더 단순하게

    • void update(int number);
    • 이는 Subject 역할까지 생략한 것인데 하나의 Observer 역할이 복수의 Subject 역할을 관찰하는 경우에는 부적절함, 전달된 수가 어떤 Subject 역할의 정보인지 모르기 때문에

관찰하기보다 전달받길 기다린다

  • Observer는 능동적으로 관찰하는 것이 아니고, Subject 역할로부터 전달되는 것을 수동적으로 기다리고 있음

  • 그래서 이를 Publish-Subscribe 패턴이라고도 함, publish(발행)와 subscribe(구독) 정도

Model/View/Controller(MVC)

  • MVC 안에 Model과 View의 관계는 Observer 패턴의 Subject 역할과 Observer 역할의 관계에 대응함

  • Model은 표시 형식에 의존하지 않는 내부 모델을 조작하는 부분임

  • 또 View는 Model이 어떻게 보일 것인지를 관리하고 있는 부분임

  • 일반적으로 하나의 Model에 복수의 View가 대응함


안드로이드?

  • LiveData와 ViewModel 자체가 Observer 패턴을 활용해서 쓸 수 있는 방식임

  • 앱에서는 데이터의 변화가 끊임없이 일어나는데 이때 마다 앱의 UI(Activity나 Fragment등)를 갱신시켜야 하는데 이때 LiveData를 사용하여서 UI를 자동으로 갱신해 줄 수 있음

  • 즉 위의 예시에서 LiveData가 Observer 역할을 함, 각각 View에 대해서 마치 Observer에서 update를 통해서 해당 클래스 내용을 갱신해주듯이 ViewModel에서 LiveData를 관찰하는 것임

  • 이렇게 하면 UI 즉 Activity등 Fragment 등에서 원래 기존 방식은 UI와 Data가 종속된 형태로 Activity와 Fragment 내부에서 처리를 했지만 이를 분리시켜 View는 View만을 표현하는 로직을 실질적인 데이터는 이 ViewModel을 통해서 LiveData로 update를 하면서 해당 UI에 맞게 갱신을 해주는 것이 가능해짐

  • 이를 구조화 시키면 아래와 같음

  • 직접적인 예시를 써본다면 만약 3개의 다른 Fragment 화면에 변화를 Observe하고 이것이 같은 Activity에서 보여지는 것이고 같은 ViewModel을 쓴다고 할 때 아래처럼 예시를 들면

 public class SharedViewModel extends ViewModel {
    private final MutableLiveData<Item> selected = new MutableLiveData<Item>();

    public void select(Item item) {
       selected.setValue(item);
    }

    public LiveData<Item> getSelected() {
        return selected;
     }
}
  • ViewModel은 우리 예시 프로그램에서의 Observer로 볼 수 있음 그리고 그걸 select 즉, View에서 이 ViewModel을 통해서 해당 View와 관련된 데이터에 대해서 통제하고 처리할 수 있음
public class ListFragment extends Fragment {
    private SharedViewModel model;

    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
       super.onViewCreated(view, savedInstanceState);
       model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
       itemSelector.setOnClickListener(item -> {
           model.select(item);
       });
       model.getSelected().observe(getViewLifecycleOwner(), item -> {
           // Update the UI.
        });

     }
}
  • 이는 마치 Subject에서 Observer로 통지하듯이 ViewModel에 의해서 Fragment에서 데이터를 통지하고 이런 처리를 LiveData가 Observer에서 update하듯이 처리할 수 있는 것임

  • 즉 ViewModel이 Observer의 역할을 그리고 Activity나 Fragment 같은 View가 Subject의 역할을 하여서 해당 데이터를 View가 직접 처리하지 않고 LiveData를 통해서 Observe 할 수 있는 것임

  • 이 방식이 생소할 수 있지만 기본적으로는 그냥 막연하게 UI단에 모든 것을 부여해서 썼지만 이 방식을 통해서 그런 비효율성을 개선할 수 있음

  • 이는 그리고 RxAndroid, Reactive Android 상에서 이런 패턴을 적용해서 활용이 가능함, 그 동작은 위에서 서술한 것과 유사하게 이루어짐(Reactive하게 반응한다는 것 자체가 Observer 패턴에 의의를 생각해보면 어느정도 일치하는 바가 있는 부분임)

profile
측정할 수 없으면 관리할 수 없고, 관리할 수 없으면 개선시킬 수도 없다

0개의 댓글